Compare commits

..

213 Commits

Author SHA1 Message Date
Safihre
29ec4d9a23 Set 2.2.1 version and update translations 2017-08-25 22:11:47 +02:00
Safihre
22517a7cd7 Merge branch '2.2.x' 2017-08-25 22:07:15 +02:00
Safihre
bcc4dd75cf Update text files for 2.2.1 2017-08-25 21:58:48 +02:00
Safihre
97711ca82e Revert "Remove locks from ArticleCache"
This reverts commit 5e7558ce4a.
2017-08-25 21:57:16 +02:00
Safihre
e782237f27 More logging when adding NZB's 2017-08-25 21:49:24 +02:00
Safihre
52bb156c08 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:38 +02:00
Safihre
4361d82ddd 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:57:47 +02:00
Safihre
017cf8f285 Do not fail a job if recursive unpack fails
The user can handle it, we did our part.
2017-08-25 09:15:14 +02:00
Safihre
03cdf6ed5d Sync translatable texts from develop
To avoid conflicts on Launchpad
2017-08-24 23:40:37 +02:00
Safihre
cf347a8e90 Original files would be deleted after a MultiPar rename 2017-08-24 23:36:36 +02:00
Safihre
f06afe43e1 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-24 16:36:13 +02:00
Safihre
fb301eb5c8 Update text files for 2.2.1RC2 2017-08-23 22:49:59 +02:00
Safihre
1562c3560b Handle '482 Download limt exceeded'
Closes #1009
2017-08-23 22:48:15 +02:00
Safihre
9813bc237f Only auto-disconnect after first run of verification 2017-08-23 21:42:56 +02:00
Safihre
b39fe059c6 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 21:42:17 +02:00
Safihre
a56c770a8b 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 16:02:01 +02:00
Safihre
e3bf0edad8 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:01 +02:00
Safihre
e35d9e4db3 Correct handeling of TryList when server has timeout 2017-08-23 08:32:47 +02:00
Safihre
c617d4321a Correctly remove + from INFO label in all languages 2017-08-22 16:13:24 +02:00
Safihre
0fd3a2881f Correct redirect after ports change 2017-08-22 10:19:42 +02:00
Safihre
0c1f7633de Only discard really non-unique hashes from md5of16k 2017-08-22 09:43:33 +02:00
Safihre
b7d5d49c84 Show hover-title that the compress icon is Direct Unpack 2017-08-22 09:43:26 +02:00
Safihre
9911b93ece Add error when NZO creation fails 2017-08-22 09:43:11 +02:00
Safihre
eeaad00968 Also hide email-accounts in logging 2017-08-22 09:43:06 +02:00
Safihre
e1bb8459e3 Take the risk of allowing up to 5 bad articles in jobs without Par2 2017-08-22 09:42:47 +02:00
Safihre
65c3ac0cc0 Warn in case the password file has too many passwords 2017-08-22 09:42:16 +02:00
Safihre
413c02a80f Do not run get_new_id forever in case of problems
#984
2017-08-22 09:41:40 +02:00
Safihre
80f118f304 UnRar is required to read some RAR files 2017-08-21 08:23:10 +02:00
Safihre
5c0a10e16b Update text files for 2.2.1RC1 2017-08-19 11:06:40 +02:00
Safihre
d9b32261e7 Reset all NZO TryList when doing Prospective Add
I thought in c14b3ed82a that this was enough, but clearly it is not.
2017-08-19 11:03:22 +02:00
Safihre
8d8ce52193 Stall prevention by checking TryList 2017-08-19 11:03:15 +02:00
Safihre
1cc2e25cda Update to 2.2.0 2017-08-17 10:47:04 +02:00
Safihre
4605c3fd30 Set version to 2.1.0 and make identical to develop 2017-06-09 11:35:46 +02:00
Safihre
ed7dc3f827 Merge branch 'develop' 2017-06-09 11:30:43 +02:00
Safihre
e69eeebdd8 Merge branch 'develop' - Update to 2.0.1 2017-05-24 12:14:56 +02:00
Safihre
5da5f1adc1 Set version to 2.0.0 2017-04-09 12:25:05 +02:00
Safihre
f47e92dec0 Merge branch 'develop' 2017-04-09 11:59:13 +02:00
Safihre
a894ca5171 Set version to 1.2.3 2017-03-18 13:05:04 +01:00
Safihre
5abe1140ae Merge branch '1.2.x' 2017-03-18 13:03:36 +01:00
Safihre
d34e14370c Chain certificates not loaded in all situations of extra servers 2017-03-18 11:43:46 +01:00
Safihre
c4f4a3131c Update 1.2.3 text files for extra bugfix 2017-03-18 11:40:57 +01:00
Safihre
dcbd9b57f3 CherryPy would fallback to (non-existing) pyopenssl for extra servers
Causing failed starts
2017-03-18 11:39:40 +01:00
Safihre
aad3b54a17 Update Text Files for 1.2.3
These patches were already tested within 2.0.0Alpha/Beta, so considerd safe.
2017-03-18 11:04:57 +01:00
Safihre
cde142a371 Windows paths can't end in a dot 2017-03-18 11:02:14 +01:00
Safihre
8bfc98ffc6 Glitter server unblock button wasn't working
Closes #864
2017-03-18 11:01:51 +01:00
Jonathon Saine
e46f21d566 Fix -w option as it no longer needs arguments. 2017-03-18 11:00:46 +01:00
Safihre
0e45fdcdfd Fix BPS manager breaking the downloader due to Quota 2017-03-18 10:28:31 +01:00
Safihre
eec7af16d7 Improve log obfuscation
It needs to match any charachters not just alpha-numeric
2017-03-18 10:28:03 +01:00
Safihre
6532425902 Kill the UnRar after a Windows failure
On Windows Server it seems unrar otherwise won't stop: https://forums.sabnzbd.org/viewtopic.php?f=3&t=22492
2017-03-18 10:27:57 +01:00
Safihre
44b896522c Unzip needs clipped paths
(7zip doesn't)
2017-03-18 10:26:32 +01:00
Safihre
1b16ee44cb Set version to 1.2.2 2017-02-28 11:17:24 +01:00
Safihre
d5f608c28c Merged branch 1.2.x into master 2017-02-28 11:16:59 +01:00
Safihre
555d8418e7 Update text files for 1.2.2 2017-02-25 22:12:34 +01:00
Safihre
8c22e35da4 Script paths were not clipped correctly 2017-02-25 22:07:25 +01:00
Safihre
95a7924b31 Set version to 1.2.1 and fix small discrepancies 2017-02-24 11:22:55 +01:00
Safihre
5830bebd95 Merge branch '1.2.x' 2017-02-24 11:16:18 +01:00
shypike
d32cf57c75 Update translations 2017-02-24 10:03:07 +01:00
Safihre
6d9242ebc5 Update text files for 1.2.1 Final 2017-02-23 11:53:56 +01:00
Safihre
cbc4f6a964 When retry of unpack due to Windows bug, send the actual password
We converted the password to "-p<password>" and when we had to retry it due to a Windows long-path fail, it would become "-p-p<password" and would incorrectly report that the password was wrong.
2017-02-21 20:09:07 +01:00
Safihre
2a3b2b9556 Forced diskspace check seperate from cached one
Closes #826. Otherwise the complete/incomplete get out of sync.
2017-02-20 15:43:55 +01:00
Safihre
53a219f12b Tooltip for Download/Incomplete in Status Windows
Closes #827
2017-02-20 09:03:45 +01:00
Safihre
48519dcfa0 Correct mistake in Diskspace calculation for Unix 2017-02-19 16:12:15 +01:00
Safihre
92542c58fe Diskspace for Complete folder was not calculated 2017-02-19 16:12:07 +01:00
Safihre
7eafe730f9 Modify RarFile to properly handle testrar on Windows
Because unrar doesn't support \\?\ notation for the path to the rarfile we used to clip the path. However, the Python functions that RarFile uses then fail on unicode or jobs with '?' in the filename. Now this is handled correctly, at the very last moment before testing the RAR.
2017-02-19 01:47:29 +01:00
Safihre
494e72a996 Update to 1.2.0 and set version 2017-01-13 17:00:51 +01:00
Safihre
84cc86f1d3 Update to 1.1.1 and set version (#731) 2016-11-11 13:58:07 +01:00
shypike
64479e2e5d Correct the merge of release 1.1.0 to the "master" branch. 2016-09-14 08:26:02 +02:00
shypike
13b523d9bd Merge pull request #681 from Safihre/master
Master to 1.1.0
2016-09-13 16:25:05 +02:00
Safihre
181881a21b Merge branch 'develop' 2016-09-11 22:16:42 +02:00
shypike
86d11095ac Merge pull request #592 from sabnzbd/1.0.x
Update master to 1.0.3
2016-06-10 19:01:03 +02:00
shypike
927ba3cd9d Update text files for 1.0.3 2016-06-04 13:34:11 +02:00
Safihre
6296fc1762 #568 Add code 482 to check for too-many-connections 2016-06-03 23:24:07 +02:00
shypike
60fbe44724 Support X-DNZB-PASSWORD header. 2016-06-03 22:18:56 +02:00
shypike
29e45da431 Fix NZB association for Windows.
Make sure that the second SABnzbd instance sends an UTF-8 encoded URL to the first instance.
Otherwise CherryPy will reject the API call.
2016-06-03 22:07:44 +02:00
shypike
d82e69eef4 Handle checksum error reports from unrar. 2016-06-02 23:19:57 +02:00
shypike
8c7d557252 Prevent job from hanging when adding back par2 files.
Sometimes already completed par2 files are being re-added to the queue as extra par2 files.
Because these files are already complete, there will be no attempt to download them
and as a result they will never leave the queue.
2016-06-02 22:11:59 +02:00
shypike
a56d6e5517 Merge pull request #577 from sabnzbd/1.0.x
1.0.x to master
2016-05-23 18:11:23 +02:00
shypike
7548d9e975 Correct base version number for 1.0.x releases. 2016-05-10 14:43:14 +02:00
shypike
b7e2bd9684 Update text files for 1.0.2 2016-05-03 19:39:45 +02:00
shypike
f0a243e3d3 Fix API status issues.
Remove the "ToPP" status, it serves no purpose.
Only truly deleted jobs should get the "Deleted" status,
not jobs moving from Download to PP queue.
2016-05-01 14:42:07 +02:00
shypike
6e108c9ef2 Prevent Completed and Failed jobs from getting status Deleted. 2016-04-30 11:47:03 +02:00
shypike
89edcc1924 Log the preferred character encoding 2016-04-30 11:18:32 +02:00
shypike
8a6aca47a1 Prevent stalling at 100% when QuickCheck is Off and "Download-all-pars" is On.
The repair function sent all extra par2 files back to the queue
even though they were already downloaded.
2016-04-28 22:42:51 +02:00
shypike
d03e5780b8 Fix API compatibility of queue statuses.
The new statuses TO_PP and DELETED should not be returned by the API.
Tools may not be able to handle them and they are only useful for internal purposes.
2016-04-27 12:03:37 +02:00
shypike
209d8f9b40 NNTP error 502 should not aways be interpreted as bad login.
It can also mean "too many connections".
2016-04-27 11:52:56 +02:00
shypike
c257b1be3d Update text files for 1.0.1 2016-04-26 19:57:22 +02:00
Safihre
2c48c8de2e Force MIME types for CSS and JS files
Caused problems on Windows if external programs overwriten it in registery.
See: http://forums.sabnzbd.org/viewtopic.php?f=2&t=20490
And: https://www.reddit.com/r/usenet/comments/4fkmcx/my_sab_interface_is_text_only/
2016-04-26 19:48:47 +02:00
shypike
a767ef6aed Fix API compatibility of queue.
The new statuses TO_PP and DELETED should not be returned by the API.
Tools may not be able to handle them and they are only useful for internal purposes.
2016-04-26 19:47:38 +02:00
shypike
ad61d1dd03 The pre-queue script can now return an accept value of 2, meaning immediate failure.
Supports front-ends which need the signal that an NZB has been
rejected by the pre-queue script.
2016-04-22 21:54:45 +02:00
shypike
33c3d187a0 Add start script for portable Windows installations 2016-04-22 16:44:37 +02:00
shypike
4eb486d4e2 Update text files for 1.0.0RC1 2016-04-16 15:31:48 +02:00
shypike
bfb6c167a4 Another attempt to set the default cache to 450M. 2016-04-16 15:27:35 +02:00
shypike
44abf3bdf6 Update text files for 1.0.1RC1 2016-04-15 23:12:22 +02:00
shypike
c950572592 Set default cache to 450M 2016-04-15 23:11:53 +02:00
shypike
3999cb13fd Update text files for 1.0.1RC1 2016-04-15 21:06:55 +02:00
shypike
af65075f0c Update text files for 1.0.0RC1 2016-04-15 21:05:53 +02:00
shypike
de2a2b465b Update text files for 1.0.1RC1 2016-04-13 22:41:13 +02:00
shypike
cd7a77f02d Revert "Set default cache size to 750MB on Windows and OSX."
This reverts commit 9b420e91c9.
2016-04-13 22:30:05 +02:00
shypike
f4a5394b63 Prevent creating orphan items in "incomplete" when deleting downloading jobs.
Due to previous issues where articles could be lost,
the nzf.deleted and no.deleted flags were not obeyed.
This could lead to creation of orphans when lost articles would be flushed.

Better solution: drop articles only when job is in a final state.
Also prevent NZO files from being saved when job is in "deleted" state.
2016-04-13 18:32:23 +02:00
jdfalk
3fb6a8dedb Update sabnzbd@.service
1. Added requirement for network to be up before sab starts.
2. Explicitly set service type to simple.
3. Enabled sabnzbd restart on service failure via systemd.
2016-04-13 18:22:11 +02:00
Safihre
50c8f84eba #448-#126 Forced item with missing articles caused overflow in paused queue
Closes #448
Closes #126
2016-04-13 18:21:41 +02:00
Safihre
2c7ecdee92 #464 Grabbing items don't always have status=grabbing
But now they do!
2016-04-07 21:45:28 +02:00
Safihre
72390a793a Add Optional label to Retry password 2016-04-07 21:45:17 +02:00
Safihre
04ad4e5d3e Update cache text to more 2016 values 2016-04-07 21:45:05 +02:00
Safihre
5ef9c6a433 #530 do not ignore files in QuickCheck
Par2 wouldn't ignore them either
2016-04-07 21:44:51 +02:00
shypike
e6baffc839 Prevent API crashes when 'mode' or 'name' have double entries. 2016-04-07 21:35:07 +02:00
shypike
e361eb25a5 Fix potential race condition in BPSmeter. 2016-04-01 21:18:19 +02:00
shypike
9b420e91c9 Set default cache size to 750MB on Windows and OSX.
To make handling of posts with large files more efficient.
2016-04-01 21:09:15 +02:00
Safihre
3a4bf971b2 #529 Fix unicode strip in OptionStr 2016-04-01 20:47:11 +02:00
Safihre
1128691c5d #527 Fix "Download all par2 files" behavior 2016-03-29 21:03:48 +02:00
Safihre
15043aef3f DB Strings should be encapsulated in ' not " 2016-03-29 21:03:30 +02:00
savef
2a3b4afa03 Treat ambiguous numeric values as number of minutes for custom pause time.
Currently if you just type "100" into the custom pause field it'll think you want 143015 minutes, that's useless. A lot of people are probably used to the old Plush behaviour of entering the number of minutes you want to pause for, it's also a much saner default. So in the case that the user just enters some numbers and nothing else, this assumes they want to pause for that many minutes.
2016-03-24 21:01:04 +01:00
shypike
00a98efa81 Fix race-condition when deleting an actively downloading job.
Closes #237
2016-03-24 20:57:27 +01:00
shypike
f013dd7f0d Accept MIME records that have only LF line endings.
Some tool developers just ignore the rule requiring CRLF.
2016-03-23 23:29:12 +01:00
shypike
7b91b1c769 Fix race condition when API and postprocessor both want to delete a history item. 2016-03-23 23:03:28 +01:00
shypike
5583cce322 When urlgrabber receives a 404, stop trying. 2016-03-23 23:00:38 +01:00
shypike
b995c5f992 Fix PushOver support.
Adjust priority levels to the current PushOver API.
Re-enable device field now that the PushOver API supports readable device labels.
2016-03-22 21:25:05 +01:00
shypike
214ac4a53d Prevent incompatibility due to missing 'script_log' field.
Fixes commit c0f2f59fc1
2016-03-20 15:24:07 +01:00
shypike
fc7e87f0df Merge branch '1.0.x' 2016-03-19 11:49:11 +01:00
Safihre
c0f2f59fc1 Fix breaking Glitter bug with large script_log 2016-03-18 14:11:54 +01:00
shypike
b90a847a6f Fix omission in README.mkd 2016-03-15 21:42:16 +01:00
shypike
a58bb385f5 Update text files for 1.0.0 2016-03-15 20:12:50 +01:00
shypike
9754baeb1c Fix handling of changed "ignore_samples" option.
Closes #510
2016-03-15 20:08:35 +01:00
shypike
ffcd154966 Update text files for 1.0.0 Final. 2016-03-14 19:12:59 +01:00
shypike
97cfe9488c Update text files for 1.0.0RC5 2016-03-11 19:24:27 +01:00
shypike
374b6f616a Fix crash in CherryPy when it reports problems with some IPv6 addresses.
A bug in Python's traceback logging causes a crash when an IPv6 address with an embedded % is reported.
2016-03-11 19:22:49 +01:00
Sander Jonkers
e2f51595b6 Because of dual IPv4/IPv6 clients, finding the public ipv4 needs special attention 2016-03-10 22:52:22 +01:00
shypike
04091a16aa Fix display of SABnzbd's icon by OSX Notification Center.
El Capitan doesn't accept the -sender parameter, so just omit it.
2016-03-08 16:40:44 +01:00
shypike
9d9d2fd9a2 Revert "Fix Plush dashboard"
This reverts commit 42f1a4926c.
2016-03-08 16:00:24 +01:00
shypike
5746115331 Suppress errors/warnings about bad "Rating" files when the feature is disabled. 2016-03-08 15:53:52 +01:00
Safihre
42f1a4926c Fix Plush dashboard 2016-03-05 11:23:19 +01:00
Safihre
7d87fd461b Fix Glitter display in <1MB/s range 2016-03-05 11:22:57 +01:00
shypike
1ba9976979 Update text files for 1.0.0RC4 2016-03-05 08:52:09 +01:00
shypike
659c199043 Fix --ipv6_hosting option.
Repairs commit 1cbff28
2016-03-05 08:50:13 +01:00
shypike
81a3f53226 Update text files for 1.0.0RC3 2016-03-04 20:39:01 +01:00
shypike
1cbff28f67 Disable listening on IPv6 addresses by the internal web server.
Setting Config->Special->ipv6_hosting to 1 will enable IPv6 listening.
Command line option --ipv6_hosting allows forcing the choice, should SABnzbd not start.
Closes #492
2016-03-04 19:26:50 +01:00
shypike
8e15acbf30 Update translations 2016-03-04 18:55:20 +01:00
Safihre
e07be60db6 #489 wrongly encoded & in the preload 2016-03-01 22:20:51 +01:00
Safihre
539c9662ff 'Default' not translated in Server-category 2016-03-01 22:20:35 +01:00
shypike
b396014f8d Fix IP test at startup.
Correction of problem introduced by commit afff88b "Use self-test.sabnzbd.org for connection tests".
2016-02-24 22:30:02 +01:00
shypike
1db32415b6 Update translations 2016-02-24 21:37:22 +01:00
shypike
b24629db6b Update text files for 1.0.0RC2 2016-02-24 21:11:43 +01:00
Safihre
9b5cdcf8fb Add trailing "/" to href's 2016-02-24 21:05:12 +01:00
shypike
4831415d14 Use self-test.sabnzbd.org for connection tests 2016-02-24 20:34:04 +01:00
Chris Thorn
a4c51f0b20 Parse bandwidth limit as a float instead of an integer so that non-integer values can be used as bandwidth limit 2016-02-24 20:09:02 +01:00
shypike
ec3ba1fb93 Changing server priorities during a download could lead to unexpected results and lockups.
Target priority of articles must be kept at the TryList level so that they
are reset when the try_list is reset.
Closes #378
2016-02-24 20:08:51 +01:00
Safihre
61966f7036 Plush dashboard local IPv4 wouldn't show if external failed
Typo!
For 1.0.0
2016-02-24 20:08:38 +01:00
Safihre
4f69e81841 Add link to information about SSL/yEnc 2016-02-24 20:07:48 +01:00
Safihre
d0d90581df Tweak first Config page 2016-02-24 20:07:35 +01:00
Safihre
8ea5c27633 Improve display of message on blocked server 2016-02-24 20:07:16 +01:00
shypike
517500fdf3 Add link to issue list on support site. 2016-02-19 22:42:30 +01:00
shypike
c4c1c9b6ab Prevent UI errors due to history_db handle not being available. 2016-02-19 22:22:42 +01:00
Safihre
2388889ede Filegrabber did not sanatize filename 2016-02-19 22:21:28 +01:00
Safihre
55cfe878d7 Catch failing Windows Notification
I assumed Windows notifications could not fail, but clearly they can:
http://forums.sabnzbd.org/viewtopic.php?f=11&t=20211&p=104438
When no tray-icon is available, it will fail and in the case of
completed NZB message it will restart the whole of SABnzbd.
2016-02-17 21:25:42 +01:00
Safihre
a2daaee468 Add Pystone score to Glitter status 2016-02-17 21:06:43 +01:00
Safihre
2c360e395e Limit to max 250 items per page (realistic limit) 2016-02-17 21:05:55 +01:00
Jonathon Saine
399cfee594 Add pyOpenSSL info to startup/utility/config base. Add OpenSSL & yEnc to config base as well. 2016-02-17 21:04:13 +01:00
shypike
be646ae6ab Set default for https verification to off. 2016-02-17 20:50:24 +01:00
shypike
b470253d9f Disable https verification when uploading NZB to running instance of SABnzbd. 2016-02-13 16:12:04 +01:00
shypike
b83c493492 Update translations 2016-02-11 01:06:52 +01:00
Safihre
991277bb01 Glitter broke on empty preload strings
See
http://forums.sabnzbd.org/viewtopic.php?f=11&t=20192&p=104368#p104368
In case of UnicodeError
2016-02-10 22:59:11 +01:00
Safihre
5626013b81 Don't allow name change or filesview on grabbing 2016-02-10 22:58:56 +01:00
Safihre
2810d37758 Make disabled servers look more disabled 2016-02-10 22:58:40 +01:00
Safihre
c2f08f01e0 #408 Show red when Add-NZB left empty 2016-02-10 22:57:30 +01:00
Safihre
17ff087e06 Do not confirm clearing warnings 2016-02-05 23:36:47 +01:00
Safihre
77de565b7c Link in message about Orphans was broken 2016-02-05 23:36:36 +01:00
Safihre
54d238aa4d Message labels were not translated
INFO and WARNING
2016-02-05 23:36:23 +01:00
Safihre
379d09f8cc Improve message about no localStorage
It happens more than I expected, so better make a proper message.
2016-02-05 23:35:23 +01:00
shypike
00de72b127 Update text files for 1.0.xRC1 2016-02-03 21:18:42 +01:00
shypike
f9c84fa7dd Fix trouble with disk speed meter (especially on Windows) Part 2.
Improve the benchmark by reducing Python overhead by writing larger blocks.
2016-02-03 21:08:47 +01:00
shypike
c8e46691bb Solve file name encoding issues for OSX.
- Names returned by unrar-commandline need to be Unicoded.
- For yEnc embedded names, only test for UTF-8 and CP1252, but not the local codepage.
- Suppress some bogus warnings
- Log the output of the unrar tool
2016-02-03 20:59:12 +01:00
shypike
df1bb636e5 Fix links in text files to pooit to 1.0.0 entries in the Wiki. 2016-02-03 20:51:19 +01:00
shypike
ff886fad0d Fix all links in the templates to point to 1.0.0 (or 1-0) entries in the Wiki. 2016-02-03 20:50:13 +01:00
shypike
6dbee7a413 Fix trouble with disk speed meter (especially on Windows) Part 2.
Move fix outside of the measurement loop.
Don't remove the test-file within the loop, but only after it's finished,
otherwise we'll still get a "Permission denied".
2016-01-30 10:52:28 +01:00
shypike
3f8fcd7172 Fix trouble with disk speed meter (especially on Windows).
Better handling when folder is not available or writable.
Windows requires some access outside of the Python code to avoid "permission denied".
2016-01-29 23:54:32 +01:00
shypike
d94f7388e6 Add a link on the main Config page, to the "issues" page on the Wiki 2016-01-29 19:55:04 +01:00
Safihre
ad8b49fea8 #408 Firefox-fix: word-wrap instead of word-break 2016-01-29 19:21:59 +01:00
Safihre
ce00270c12 #447 only show arguments-field for speedlimit 2016-01-29 19:21:41 +01:00
Safihre
8c501f8f58 #447 Servers in scheduler more clear 2016-01-29 19:21:30 +01:00
Safihre
ce313ebc65 #445 Reduce statusinfo timeout on startup 2016-01-29 19:21:18 +01:00
Safihre
887ad881a2 #444 HTTPS instead of HTTP for RSS favicon 2016-01-29 19:21:07 +01:00
Safihre
ce40827552 Fix breaking RSS page
Fixes http://forums.sabnzbd.org/viewtopic.php?f=11&t=20114
2016-01-29 19:20:52 +01:00
shypike
2777d89482 Fix typos that prevented notifications about disk full being sent. 2016-01-28 23:19:36 +01:00
shypike
727b300a0e Update translations 2016-01-24 16:34:40 +01:00
shypike
652b021a8e Update text files for 0.8.0Beta6 2016-01-24 16:26:26 +01:00
shypike
fdf33acfbb Allow "None" as selection for secondary skin. 2016-01-24 16:15:02 +01:00
shypike
b001bc9b6f Prevent multiple resume notifications.
Only report "Resume" when coming from Paused mode.
2016-01-24 15:18:24 +01:00
shypike
8802cb1d8c Notifications template contained two instances of "ncenter_enable", leading to problems.
The result was a list instead of a single value.
2016-01-24 15:18:07 +01:00
shypike
e19a2fbae7 Improve handling of an old queue when upgrading to 0.8.0+
Warn only for a non-empty queue.
For Windows, when using a relative "download_dir" based on the user profile,
prepend the path with "Documents".
No action when the -f parameter was used.
This way it will be easier to restore existing jobs.
2016-01-24 15:17:47 +01:00
Safihre
53e38f98f9 Firefox needs more time to process the Server AJAX 2016-01-24 15:16:52 +01:00
Safihre
e783e227f6 #438 Try to stop Firefox from checking checkboxes 2016-01-24 15:16:39 +01:00
shypike
f3dfbe4181 Fix problem where a stray RAR file would cause a failed unpack run.
When a job of which the RAR files are renamed by par2,
needs repair of one more more files, the original damaged files will stay behind.
This will cause SABnzbd to try a doomed attempt at unpacking.
SABnzbd should keep track of such files and delete them after repair.
2016-01-24 15:15:57 +01:00
shypike
bcd8ca8bc4 Fix potential crash when reverting par2 renames.
Can happen when files have been modified outside of SABnzbd's control.
2016-01-24 15:12:06 +01:00
shypike
816d6a63cd For SSL protocol choice, default to auto-negotiation.
The "V23" method is interpreted by OpenSSL as "negotiate the highest available protocol"
and should therefor be a safe choice (best chance of working and best security).
If a user wants to, it is possible to fix the protocol, to prevent interference in the negotiation.
We cannot just assume that we can use our highest fixed protocol, because some Usenet servers
are being slow with implementing TLS1.2
2016-01-24 15:11:30 +01:00
shypike
88d3f25700 Perform IPv6 test on port 443 instead of 80.
Works better with some proxies.
Closes issue #274
2016-01-19 18:13:42 +01:00
shypike
80f69b11db When trying to connect to another SABnzbd instance over HTTPS, don't verify certificates.
Very few SABnzbd installations will have valid certificates.
2016-01-19 18:03:33 +01:00
Safihre
81a11f20c8 Re-order Switches page 2016-01-19 17:30:01 +01:00
Safihre
9e2a839953 Config fixes 2016-01-19 17:30:01 +01:00
Safihre
3cefcde270 #408 Refresh on Config Special save to update the * 2016-01-19 17:30:01 +01:00
Safihre
87a1eacfe7 #408 Also close history-details on history-row click
Before it would only open
2016-01-19 17:30:01 +01:00
Safihre
7cbc1a8419 #408 Browser navbar to black on mobile 2016-01-19 17:30:01 +01:00
Safihre
7b5570eb0b #408 Extra space next to Folder icon 2016-01-19 17:30:00 +01:00
Safihre
1a43a4dcf0 #432 Change filename to name in Add NZB 2016-01-19 17:30:00 +01:00
Safihre
2c2a6592c7 End of queue script was forgotten in Glitter 2016-01-19 17:30:00 +01:00
shypike
f31de6ee4e The compiled OSX build wasn't restarted with original command line arguments.
Rare use case where the App was originally started with parameters.
Essential for correct preservation of the -f parameter.
2016-01-19 17:29:11 +01:00
shypike
8fcd1f6b6c Merge branch 'develop' into R0.8.0 2016-01-16 12:25:38 +01:00
shypike
d7f3a473d7 Merge branch 'develop' into R0.8.0 2016-01-13 21:51:51 +01:00
shypike
ab2eb0c94e Update text files for 0.8.0Beta5 2016-01-13 20:17:32 +01:00
shypike
e51f4fc45a Merge pull request #181 from discordianfish/add-dockerfile
Add Dockerfile
2014-10-08 21:38:15 +02:00
Johannes 'fish' Ziemke
65278120e2 Add Dockerfile
Usage:

        docker build -t sabnzbd .
        docker run -p 127.0.0.1:8080:8080 sabnzbd
2014-09-24 13:46:43 +02:00
shypike
2eed355e9c Revert "newsunpack.py: also handle output of unrar5 with password protected files"
This reverts commit 12fd63c1cf.
2013-11-11 20:24:11 +01:00
shypike
018955f4d5 Merge pull request #122 from sanderjo/patch-4
newsunpack.py: also handle output of unrar5 with password protected file...
2013-11-11 11:19:20 -08:00
sanderjo
12fd63c1cf newsunpack.py: also handle output of unrar5 with password protected files
as discussed http://forums.sabnzbd.org/viewtopic.php?f=3&t=16166&p=89054#p89054
2013-11-07 17:02:24 +01:00
69 changed files with 9952 additions and 9587 deletions

View File

@@ -1,4 +1,4 @@
SABnzbd 2.3.0
SABnzbd 2.2.0
-------------------------------------------------------------------------------
0) LICENSE
@@ -27,38 +27,39 @@ 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 a few seconds your web browser will start and show the user interface.
Within 5-10 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 a few seconds your web browser will start and show the user interface.
Within 5-10 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.
All platforms
Unix/Linux/macOS
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
@@ -66,17 +67,18 @@ Essential modules
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"
More information: https://sabnzbd.org/sabyenc
openssl >= 1.0.0 http://www.openssl.org/
v0.9.8 will work, but limits certificate validation
cryptography >= 1.0 use "pip install cryptography"
Enables certificate generation and detection of encrypted RAR-files
Optional modules Linux
Optional modules Unix/Linux/macOS
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.
@@ -86,7 +88,6 @@ 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
@@ -94,7 +95,7 @@ Start this from a shell terminal (or command prompt):
Start this from a shell terminal (or command prompt):
python SABnzbd.py
Within a few seconds your web browser will start and show the user interface.
Within 5-10 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.
@@ -112,7 +113,7 @@ or
You may of course try other port numbers too.
For troubleshooting on Windows you can use the program SABnzbd-console.exe.
For troubleshooting 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.
@@ -120,7 +121,7 @@ may help you solve problems easier.
6) MORE INFORMATION
-------------------------------------------------------------------------------
Visit our wiki:
Visit the WIKI site:
https://sabnzbd.org/wiki/
@@ -130,4 +131,4 @@ Visit our wiki:
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

@@ -1,7 +1,7 @@
Metadata-Version: 1.0
Name: SABnzbd
Version: 2.3.0Alpha2
Summary: SABnzbd-2.3.0Alpha1
Version: 2.2.1
Summary: SABnzbd-2.2.1
Home-page: https://sabnzbd.org
Author: The SABnzbd Team
Author-email: team@sabnzbd.org

View File

@@ -1,44 +1,98 @@
Release Notes - SABnzbd 2.3.0 Alpha 2
Release Notes - SABnzbd 2.2.1
=========================================================
## 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'
## Changes since 2.2.0
- Allow up to 5 bad articles for jobs with no or little par2
- Only auto-disconnect after first run of verification
- Warning is shown when password-file is too large
- Failure of recursive unpacking no longer fails whole job
- Failure of unpacking of duplicate RAR no longer fails whole job
## Bugfixes since 2.2.0
- Some users were experiencing downloads or pre-check being stuck at 99%
- Fixed RarFile error during unpacking
- Remove email addresses settings from log export
- Block server longer on 'Download limit exceeded' errors
- Windows: If repair renamed a job the correct renamed file was deleted
- Windows: Unpacking of downloads with many archives could fail
- macOS: Adding jobs could fail without any error
## 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
Release Notes - SABnzbd 2.2.0
=========================================================
## 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'
NOTE: Due to changes in this release, the queue will be converted when 2.2.0
is started for the first time. Job order, settings and data will be
preserved, but all jobs will be unpaused and URLs that did not finish
fetching before the upgrade will be lost!
## Changes since 2.1.0
- Direct Unpack: Jobs will start unpacking during the download, reduces
post-processing time but requires capable hard drive. Only works for jobs that
do not need repair. Will be enabled if your incomplete folder-speed > 40MB/s
- Reduced memory usage, especially with larger queues
- Graphical overview of server-usage on Servers page
- Notifications can now be limited to certain Categories
- Removed 5 second delay between fetching URLs
- Each item in the Queue and File list now has Move to Top/Bottom buttons
- Add option to only tag a duplicate job without pausing or removing it
- New option "History Retention" to automatically purge jobs from History
- Jobs outside server retention are processed faster
- Obfuscated filenames are renamed during downloading, if possible
- Disk-space is now checked before writing files
- Add "Retry All Failed" button to Glitter
- Smoother animations in Firefox (disabled previously due to FF high-CPU usage)
- Show missing articles in MB instead of number of articles
- Better indication of verification process before and after repair
- Remove video and audio rating icons from Queue
- Show vote buttons instead of video and audio rating buttons in History
- If enabled, replace dots in filenames also when there are spaces already
- Handling of par2 files made more robust
- All par2 files are only downloaded when enabled, not on enable_par_cleanup
- Update GNTP bindings to 1.0.3
- max_art_opt and replace_illegal moved from Switches to Specials
- Removed Specials par2_multicore and allow_streaming
- Windows: Full unicode support when calling repair and unpack
- Windows: Move enable_multipar to Specials
- Windows: MultiPar verification of a job is skipped after blocks are fetched
- Windows & macOS: removed par2cmdline in favor of par2tbb/MultiPar
- Windows & macOS: Updated WinRAR to 5.5.0
## Bugfixes since 2.1.0
- Shutdown/suspend did not work on some Linux systems
- Standby/Hibernate was not working on Windows
- Deleting a job could result in write errors
- Display warning if "Extra par2 parameters" turn out to be wrong
- RSS URLs with commas in the URL were broken
- Fixed some "Saving failed" errors
- Fixed crashing URLGrabber
- Jobs with renamed files are now correctly handled when using Retry
- Disk-space readings could be updated incorrectly
- Correct redirect after enabling HTTPS in the Config
- Fix race-condition in Post-processing
- History would not always show latest changes
- Convert HTML in error messages
- In some cases not all RAR-sets were unpacked
- Fixed unicode error during Sorting
- Faulty pynotify could stop shutdown
- Categories with ' in them could result in SQL errors
- Special characters like []!* in filenames could break repair
- Wizard was always accessible, even with username and password set
- Correct value in "Speed" Extra History Column
- Not all texts were shown in the selected Language
- Various CSS fixes in Glitter and the Config
- Catch "error 0" when using HTTPS on some Linux platforms
- Warning is shown when many files with duplicate filenames are discarded
- Improve zeroconf/bonjour by sending HTTPS setting and auto-discover of IP
- Windows: Fix error in MultiPar-code when first par2-file was damaged
- macOS: Catch "Protocol wrong type for socket" errors
## Translations
- Added Hebrew translation by ION IL, many other languages updated.
## Depreciation notices
- Option to limit Servers to specific Categories is now scheduled
to be removed in the next release.
## Upgrading from 2.1.x and older
- Finish queue
@@ -47,12 +101,13 @@ Release Notes - SABnzbd 2.3.0 Alpha 2
- 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.
- Due to changes in this release, the queue will be converted when 2.2.x
is started for the first time. Job order, settings and data will be
preserved, but all jobs will be unpaused and URLs 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.
This version will not see the old queue, but you restore the jobs by going
to Status page and use Queue Repair.
## Known problems and solutions
- Read the file "ISSUES.txt"

View File

@@ -37,7 +37,6 @@ import getopt
import signal
import socket
import platform
import ssl
import time
import re
@@ -98,6 +97,7 @@ 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
@@ -426,12 +426,10 @@ def print_modules():
if sabnzbd.newsunpack.PAR2_COMMAND:
logging.info("par2 binary... found (%s)", sabnzbd.newsunpack.PAR2_COMMAND)
else:
logging.error('%s %s' % (T('par2 binary... NOT found!'), T('Verification and repair will not be possible.')))
logging.error(T('par2 binary... NOT found!'))
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 +440,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.info('UNRAR binary version %.2f', (float(sabnzbd.newsunpack.RAR_VERSION) / 100))
logging.debug('UNRAR binary version %.2f', (float(sabnzbd.newsunpack.RAR_VERSION) / 100))
else:
logging.error('%s %s' % (T('unrar binary... NOT found'), T('Downloads will not unpacked.')))
logging.error(T('unrar binary... NOT found'))
if sabnzbd.newsunpack.ZIP_COMMAND:
logging.info("unzip binary... found (%s)", sabnzbd.newsunpack.ZIP_COMMAND)
@@ -822,7 +820,7 @@ def main():
logging_level = None
web_dir = None
vista_plus = False
win64 = False
vista64 = False
repair = 0
api_url = None
no_login = False
@@ -947,8 +945,8 @@ def main():
# Detect Windows variant
if sabnzbd.WIN32:
vista_plus, win64 = windows_variant()
sabnzbd.WIN64 = win64
vista_plus, vista64 = windows_variant()
sabnzbd.WIN64 = vista64
if not SQLITE_DLL:
panic_sqlite(sabnzbd.MY_FULLNAME)
@@ -1027,7 +1025,7 @@ def main():
pass
else:
if not url:
url = 'https://%s:%s%s/api?' % (browserhost, port, sabnzbd.cfg.url_base())
url = 'https://%s:%s/sabnzbd/api?' % (browserhost, port)
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():
@@ -1056,7 +1054,7 @@ def main():
pass
else:
if not url:
url = 'http://%s:%s%s/api?' % (browserhost, cherryport, sabnzbd.cfg.url_base())
url = 'http://%s:%s/sabnzbd/api?' % (browserhost, cherryport)
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():
@@ -1151,12 +1149,14 @@ def main():
logging.info('Full executable path = %s', sabnzbd.MY_FULLNAME)
if sabnzbd.WIN32:
suffix = ''
if win64:
suffix = '(win64)'
if vista_plus:
suffix = ' (=Vista+)'
if vista64:
suffix = ' (=Vista+ x64)'
try:
logging.info('Platform = %s %s', platform.platform(), suffix)
logging.info('Platform=%s%s Class=%s', platform.platform(), suffix, os.name)
except:
logging.info('Platform = %s <unknown>', suffix)
logging.info('Platform=%s <unknown> Class=%s', suffix, os.name)
else:
logging.info('Platform = %s', os.name)
logging.info('Python-version = %s', sys.version)
@@ -1174,24 +1174,7 @@ 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()
@@ -1216,12 +1199,12 @@ def main():
from sabnzbd.utils.getperformance import getpystone, getcpu
pystoneperf = getpystone()
if pystoneperf:
logging.debug('CPU Pystone available performance = %s', pystoneperf)
logging.debug('CPU Pystone available performance is %s', pystoneperf)
else:
logging.debug('CPU Pystone available performance could not be calculated')
cpumodel = getcpu() # Linux only
if cpumodel:
logging.debug('CPU model = %s', cpumodel)
logging.debug('CPU model name is %s', cpumodel)
logging.info('Read INI file %s', inifile)
@@ -1265,9 +1248,12 @@ def main():
# Find external programs
sabnzbd.newsunpack.find_programs(sabnzbd.DIR_PROG)
print_modules()
# HTTPS certificate generation
logging.info("SSL version %s", sabnzbd.utils.sslinfo.ssl_version())
logging.info("SSL supported protocols %s", str(sabnzbd.utils.sslinfo.ssl_protocols_labels()))
https_cert = sabnzbd.cfg.https_cert.get_path()
https_key = sabnzbd.cfg.https_key.get_path()
https_chain = sabnzbd.cfg.https_chain.get_path()
@@ -1283,7 +1269,6 @@ 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')
@@ -1336,7 +1321,6 @@ 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,
@@ -1373,7 +1357,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.cfg.url_base(), config=appconfig)
cherrypy.tree.mount(main_page, '/sabnzbd/', config=appconfig)
# Set authentication for CherryPy
sabnzbd.interface.set_auth(cherrypy.config)
@@ -1391,14 +1375,21 @@ def main():
# Wait for server to become ready
cherrypy.engine.wait(cherrypy.process.wspbus.states.STARTED)
# Window Service support
# 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)
mail = None
if sabnzbd.WIN32:
if enable_https:
mode = 's'
else:
mode = ''
api_url = 'http%s://%s:%s%s/api?apikey=%s' % (mode, browserhost, cherryport, sabnzbd.cfg.url_base(), sabnzbd.cfg.api_key())
api_url = 'http%s://%s:%s/sabnzbd/api?apikey=%s' % (mode, browserhost, cherryport, sabnzbd.cfg.api_key())
if sabnzbd.WIN_SERVICE:
mail = MailSlot()
@@ -1431,9 +1422,9 @@ def main():
# Set URL for browser
if enable_https:
browser_url = "https://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
browser_url = "https://%s:%s/sabnzbd" % (browserhost, cherryport)
else:
browser_url = "http://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
browser_url = "http://%s:%s/sabnzbd" % (browserhost, cherryport)
sabnzbd.BROWSER_URL = browser_url
if not autorestarted:
@@ -1447,13 +1438,6 @@ 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:
@@ -1702,8 +1686,9 @@ 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
@@ -1728,7 +1713,9 @@ if __name__ == '__main__':
sabApp = startApp()
sabApp.start()
AppHelper.runEventLoop()
except:
main()
else:
main()

View File

@@ -45,7 +45,6 @@
// 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 '/'#'
@@ -62,7 +61,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.2.1.min.js?v=$version"></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/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

@@ -30,7 +30,7 @@
<tr>
<th scope="row">OpenSSL:</th>
<td>
$ssl_version
$ssl_version &nbsp; [$ssl_protocols]
</td>
</tr>
<!--#if not $have_ssl_context#-->

View File

@@ -59,13 +59,6 @@ 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

@@ -78,6 +78,17 @@
<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" />
@@ -241,6 +252,21 @@
<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')<br><span class="label label-warning">$T('warning').upper()</span> <strong>This option is scheduled to be removed in the next release of SABnzbd.</strong></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']" />
@@ -398,6 +424,33 @@
}, 100)
})
/**
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
}
}
})
// We found nothing.. Let's show a warning
if(!hasDefault) \$('.alert-no-category').show()
}
\$('select[name="categories"]').on('change', checkServerCats)
checkServerCats()
/**
Click events
**/

View File

@@ -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="$T('decade')" />
<input type="button" class="btn btn-default" onclick="movieSet('%0decade/%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="Decades 1" />
</div>
</div>
<div id="previewmovie" class="example">

View File

@@ -104,11 +104,6 @@
</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">

View File

@@ -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.2.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/js/jquery-3.1.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
</head>
<html>

View File

@@ -1067,6 +1067,7 @@ input[type="checkbox"] {
display: none;
}
.alert-no-category,
.alert-translate {
display: none;
margin: 5px 0px 0px;

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,13 +226,15 @@ 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') == ($('#enable_https').data('original') === undefined))
var portsUnchanged = ($('#port').val() == $('#port').data('original')) && ($('#https_port').val() == $('#https_port').data('original'))
// Are we on settings page or did nothing change?
if(!$('body').hasClass('General') || (!switchedHTTPS && portsUnchanged)) {
// Same as before
var urlTotal = window.location.origin + urlBase
var urlTotal = window.location.origin + urlPath
} else {
// Protocol and port depend on http(s) setting
if($('#enable_https').is(':checked') && (window.location.protocol == 'https:' || !$('#https_port').val())) {
@@ -246,7 +248,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 + urlBase;
var urlTotal = urlProtocol + '//' + window.location.hostname + ':' + urlPort + urlPath;
}
// Show where we are going to connect

View File

@@ -39,11 +39,6 @@
<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>
@@ -88,7 +83,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: queueIcon"></span>
<span class="hover-button glyphicon" data-bind="css: { 'glyphicon-play': pausedStatus(), 'glyphicon-pause': !pausedStatus() }"></span>
</a>
</td>
<td class="name">
@@ -121,8 +116,8 @@
</td>
<td class="progress-indicator">
<div class="progress">
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: ' + percentage() + '%; background-color: ' + progressColor() + ';' }">
<strong data-bind="text: percentage() + '%'"></strong>
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: ' + percentageRounded() + '; background-color: ' + progressColor() + ';' }">
<strong data-bind="text: percentageRounded"></strong>
<i data-bind="text: missingText"></i>
</div>
<span data-bind="text: progressText"></span>

View File

@@ -478,12 +478,11 @@ 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' || data.avg_age == '-')
self.isGrabbing = ko.observable(data.status == 'Grabbing')
self.totalMB = ko.observable(parseFloat(data.mb));
self.remainingMB = ko.observable(parseFloat(data.mbleft))
self.missingMB = ko.observable(parseFloat(data.mbmissing))
self.percentage = ko.observable(parseInt(data.percentage))
self.remainingMB = ko.observable(parseFloat(data.mbleft));
self.avg_age = ko.observable(data.avg_age)
self.missing = ko.observable(parseFloat(data.mbmissing))
self.direct_unpack = ko.observable(data.direct_unpack)
self.category = ko.observable(data.cat);
self.priority = ko.observable(parent.priorityName[data.priority]);
@@ -503,8 +502,8 @@ function QueueModel(parent, data) {
if(self.status() == 'Checking') {
return '#58A9FA'
}
// Check for missing data, the value is arbitrary! (2%)
if(self.missingMB()/self.totalMB() > 0.02) {
// Check for missing data, the value is arbitrary! (3%)
if(self.missing()/self.totalMB() > 0.03) {
return '#F8A34E'
}
// Set to grey, only when not Force download
@@ -515,16 +514,22 @@ function QueueModel(parent, data) {
return '';
});
// MB's
// 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))
})
self.progressText = ko.pureComputed(function() {
return (self.totalMB() - self.remainingMB()).toFixed(0) + " MB / " + (self.totalMB() * 1).toFixed(0) + " MB";
return self.downloadedMB() + " MB / " + (self.totalMB() * 1).toFixed(0) + " MB";
})
// Texts
self.missingText= ko.pureComputed(function() {
// Check for missing data, the value is arbitrary! (1%)
if(self.missingMB()/self.totalMB() > 0.01) {
return self.missingMB().toFixed(0) + ' MB ' + glitterTranslate.misingArt
// Check for missing data, the value is arbitrary! (3%)
if(self.missing()/self.totalMB() > 0.03) {
return self.missing().toFixed(0) + ' MB ' + glitterTranslate.misingArt
}
return;
})
@@ -541,18 +546,6 @@ 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?
@@ -585,12 +578,11 @@ function QueueModel(parent, data) {
self.password(data.password);
self.index(data.index);
self.status(data.status)
self.isGrabbing(data.status == 'Grabbing' || data.avg_age == '-')
self.isGrabbing(data.status == 'Grabbing')
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(parseFloat(data.mbmissing))
self.direct_unpack(data.direct_unpack)
self.category(data.cat);
self.priority(parent.priorityName[data.priority]);

View File

@@ -1,13 +1,7 @@
// Peity jQuery plugin version 3.2.1
// (c) 2016 Ben Pickles
// Peity jQuery plugin version 3.2.0
// (c) 2015 Ben Pickles
//
// http://benpickles.github.io/peity
//
// Released under MIT license.
(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);
!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);

View File

@@ -1,124 +1,123 @@
/*!
* Knockout JavaScript library v3.4.2
* (c) The Knockout.js team - http://knockoutjs.com/
* Knockout JavaScript library v3.4.0
* (c) Steven Sanderson - http://knockoutjs.com/
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
*/
(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)})()})})();})();
(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)})()})})();})();

View File

@@ -527,7 +527,6 @@ tbody>tr>td:last-child {
}
.hover-button.glyphicon-play,
.hover-button.glyphicon-forward,
.hover-button.glyphicon-stop {
opacity: 1;
color: #474747;

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.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>
<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>
</td>
<!--#if $rating_enable#-->

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,2 @@
dojo.hostenv.conditionalLoadModule({"common": ["MochiKit.MochiKit"]});
dojo.hostenv.moduleLoaded("MochiKit.*");
dojo.hostenv.conditionalLoadModule({"common": ["MochiKit.MochiKit"]});
dojo.hostenv.moduleLoaded("MochiKit.*");

View File

@@ -6,7 +6,7 @@
<link rel="stylesheet" type="text/css" href="../staticcfg/bootstrap/css/bootstrap.min.css?v=$version"/>
<link rel="stylesheet" type="text/css" href="static/style.css?v=$version"/>
<link rel="shortcut icon" href="../staticcfg/ico/favicon.ico?v=$version" />
<script type="text/javascript" src="../staticcfg/js/jquery-3.2.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/js/jquery-3.1.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
</head>
<body>

View File

@@ -1,11 +1,11 @@
The original author of SABnzbd based his work on Pynewsleecher by Freddy@madcowdesease.org.
Few parts of Pynewsleecher have survived the generations of SABnzbd in a
recognizable form.
Still, we wish to thank Freddy for his inspiration.
The home of the Pynewsleecher project:
http://www.madcowdisease.org/mcd/pynewsleecher
The software does not carry any license information.
The original author of SABnzbd based his work on Pynewsleecher by Freddy@madcowdesease.org.
Few parts of Pynewsleecher have survived the generations of SABnzbd in a
recognizable form.
Still, we wish to thank Freddy for his inspiration.
The home of the Pynewsleecher project:
http://www.madcowdisease.org/mcd/pynewsleecher
The software does not carry any license information.

View File

@@ -1,8 +1,8 @@
On http://www.brunningonline.net/simon/blog/archives/001835.html,
the author licensed SysTrayIcon.py under a variant of the WTFPL:
> Any road up, help yourself. Consider SysTrayIcon.py to be under an
> "Aleister Crowley" style license - "Do what thou wilt shall be the
> only law".
>
> Err, but don't sue me if it doesn't work. ;-)
On http://www.brunningonline.net/simon/blog/archives/001835.html,
the author licensed SysTrayIcon.py under a variant of the WTFPL:
> Any road up, help yourself. Consider SysTrayIcon.py to be under an
> "Aleister Crowley" style license - "Do what thou wilt shall be the
> only law".
>
> Err, but don't sue me if it doesn't work. ;-)

View File

@@ -12,7 +12,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 7bit\n"
"POT-Creation-Date: 2017-09-04 13:27+W. Europe Daylight Time\n"
"POT-Creation-Date: 2017-08-25 09:18+W. Europe Daylight Time\n"
"Generated-By: pygettext.py 1.5\n"
@@ -443,19 +443,18 @@ msgstr ""
msgid "Jobs will start unpacking during the downloading to reduce post-processing time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr ""
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr ""
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr ""
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr ""
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr ""
@@ -945,6 +944,11 @@ msgstr ""
msgid "Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr ""
@@ -953,10 +957,6 @@ msgstr ""
msgid "Fetching"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -987,10 +987,6 @@ msgstr ""
msgid "Checking"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Checking extra files"
msgstr ""
#: sabnzbd/newsunpack.py [Error message]
msgid "Python script \"%s\" does not have execute (+x) permission set"
msgstr ""
@@ -1148,6 +1144,10 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbstuff.py [Warning message]
msgid "%d files with duplicate filenames were discared for \"%s\". Enable \"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr ""
@@ -4261,10 +4261,6 @@ msgstr ""
msgid "Pause for..."
msgstr ""
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr ""
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr ""
@@ -4321,6 +4317,10 @@ msgstr ""
msgid "You must enable JavaScript for Plush to function!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr ""
#: sabnzbd/skintext.py
msgid "Options"
msgstr ""

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2017-06-22 07:07+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>\n"
"Language-Team: Danish <da@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:49+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -471,19 +471,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Kan ikke læse %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Det lykkedes ikke at tilføje %s, slette"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Fejl ved fjernelse af %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Kan ikke læse %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Det lykkedes ikke at tilføje %s, slette"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Kan ikke læse overvåget mappe %s"
@@ -1014,6 +1013,11 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Reparation mislykkedes, ikke nok reparation blokke (%s mangler)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Henter %s block..."
@@ -1022,10 +1026,6 @@ msgstr "Henter %s block..."
msgid "Fetching"
msgstr "Henter"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Reparation mislykkedes, ikke nok reparation blokke (%s mangler)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1215,6 +1215,12 @@ msgstr "Dublet NZB"
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pause duplikeret NZB \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Afbrudt, kan ikke afsluttes"
@@ -4549,10 +4555,6 @@ msgstr "Vi kunne desværre ikke fortolke denne. Prøv igen."
msgid "Pause for..."
msgstr "Pause i..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Opdatere"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Sortere efter alder <small>Ældst&rarr;Nyeste</small>"
@@ -4609,6 +4611,10 @@ msgstr "Vil du virkelig tømme historiken?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Du skal aktivere JavaScript for at få Plush til virke!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Opdatere"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Muligheder"
@@ -4915,15 +4921,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"For at hente fra usenet kræves der adgang fra en udbyder. Din "
"internetudbyder kan give dig adgang, men en præmie udbyder anbefales."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Har du ingen usenet leverandør? Vi anbefaler at prøve %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5258,6 +5259,16 @@ msgstr "URL hentning mislykkedes; %s"
#~ "Launch my internet browser with the SABnzbd page when the program starts."
#~ msgstr "Start webbrowseren med SABnzbd's siden når programmet startes."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "For at hente fra usenet kræves der adgang fra en udbyder. Din "
#~ "internetudbyder kan give dig adgang, men en præmie udbyder anbefales."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Har du ingen usenet leverandør? Vi anbefaler at prøve %s."
#~ msgid "Please enter a whole number."
#~ msgstr "Angiv et helt tal."

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2017-08-24 15:39+0000\n"
"Last-Translator: jcfp <Unknown>\n"
"Language-Team: German <de@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:49+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -493,19 +493,18 @@ msgstr ""
"Nachbearbeitungszeit zu verkürzen. Nur für Aufträge, die nicht repariert "
"werden müssen."
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "%s kann nicht gelesen werden"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Fehler beim Hinzufügen von %s. Entferne."
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Fehler beim Entfernen von %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "%s kann nicht gelesen werden"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Fehler beim Hinzufügen von %s. Entferne."
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Überwachter Ordner %s kann nicht gelesen werden"
@@ -1047,6 +1046,13 @@ msgstr ""
"Ungültige par2-Dateien oder ungültige PAR2-Parameter, Auftrag konnte nicht "
"überprüft oder repariert werden"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Reparatur fehlgeschlagen. Nicht genug Reparatur-Blöcke vorhanden (%s zu "
"wenig)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "%s Blöcke werden abgerufen …"
@@ -1055,12 +1061,6 @@ msgstr "%s Blöcke werden abgerufen …"
msgid "Fetching"
msgstr "Abrufen"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Reparatur fehlgeschlagen. Nicht genug Reparatur-Blöcke vorhanden (%s zu "
"wenig)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1258,6 +1258,14 @@ msgstr "Doppelte NZB"
msgid "Pausing duplicate NZB \"%s\""
msgstr "Doppelt vorhandene NZB \"%s\" angehalten"
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
"%d Dateien mit doppelten Dateinamen wurden für \"%s\" verworfen. Aktiviere "
"\"allow_duplicate_files\" um doppelte Dateinamen zu erlauben."
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Abgebrochen, kann nicht fertiggestellt werden"
@@ -4657,10 +4665,6 @@ msgstr "Sorry, damit konnten wir nichts anfangen. Versuchs nochmal."
msgid "Pause for..."
msgstr "Anhalten für …"
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Neu laden"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Sortieren nach Alter <small>Älteste&rarr;Neuste</small>"
@@ -4717,6 +4721,10 @@ msgstr "Verlauf wirklich leeren?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Sie müssen JavaScript aktivieren, damit Plush funktioniert!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Neu laden"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Optionen"
@@ -5025,16 +5033,14 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"Um aus dem Usenet herunterladen zu können, benötigen Sie Zugriff auf einen "
"Usenet-Provider. Ihr ISP bieten dies möglicherweise an, jedoch werden "
"kostenpflichtige Provider empfohlen."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Wenn Sie noch keinen Usenet-Provider haben, empfehlen wir Ihnen %s."
"Um aus dem Usenet herunterzuladen, wird ein Zugang zu einem Provider "
"benötigt. Dein ISP kann dir Zugriff gewähren, allerdings wird ein Premium-"
"Anbieter empfohlen. Noch keinen Usenet-Provider? Wir empfehlen, %s zu "
"versuchen."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5440,6 +5446,17 @@ msgstr "Abrufen der URL fehlgeschlagen; %s"
#~ "Launch my internet browser with the SABnzbd page when the program starts."
#~ msgstr "SABnzbd in meinem Webbrowser öffnen, wenn es gestartet wird."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "Um aus dem Usenet herunterladen zu können, benötigen Sie Zugriff auf einen "
#~ "Usenet-Provider. Ihr ISP bieten dies möglicherweise an, jedoch werden "
#~ "kostenpflichtige Provider empfohlen."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Wenn Sie noch keinen Usenet-Provider haben, empfehlen wir Ihnen %s."
#~ msgid "This field is required."
#~ msgstr "Dieses Feld wird benötigt."

View File

@@ -105,15 +105,11 @@ msgstr "Posts will be paused until they are at least this age. Setting job prior
msgid "Support the project, Donate!"
msgstr "Support the project, donate!"
msgid "%d files with duplicate filenames were discared for \"%s\". Enable \"allow_duplicate_files\" to allow duplicate filenames."
msgstr "%d files with duplicate filenames were discarded for \"%s\". Enable \"allow_duplicate_files\" to allow duplicate filenames."
msgid "User script can flag job as failed"
msgstr "Post-processing script can flag job as failed"
msgid "When the user script returns a non-zero exit code, the job will be flagged as failed."
msgstr "When the post-processing script returns a non-zero exit code, the job will be flagged as failed."
msgid "unrar binary... NOT found"
msgstr "unrar binary... NOT found!"
msgid "Downloads will not unpacked."
msgstr "Downloads will not be unpacked."

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2017-06-22 07:07+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>\n"
"Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:50+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:50+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -472,19 +472,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "No se puede leer %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Error al añadir %s, eliminando"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Error al quitar %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "No se puede leer %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Error al añadir %s, eliminando"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Directorio Watched %s no se puede leer"
@@ -1020,6 +1019,13 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Ha fallado la reparación, no existen bloques de reparación suficientes (%s "
"short)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Recuperando %s bloques..."
@@ -1028,12 +1034,6 @@ msgstr "Recuperando %s bloques..."
msgid "Fetching"
msgstr "Recuperando"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Ha fallado la reparación, no existen bloques de reparación suficientes (%s "
"short)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1226,6 +1226,12 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausando NZB duplicados \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Abortado, No puede ser completado"
@@ -4574,10 +4580,6 @@ msgstr ""
msgid "Pause for..."
msgstr "Pausar durante..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Actualizar"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Ordenar por Fecha <small>Más viejo&rarr;Más nuevo</small>"
@@ -4634,6 +4636,10 @@ msgstr "¿Vaciar el historial?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Debes activar JavaScript para que pueda funcionar Plush!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Actualizar"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Opciones"
@@ -4940,16 +4946,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"Para poder descargar de Usenet, necesitas acceso con un proveedor. Tu "
"proveedor de acceso a internet te lo puede dar, aunque recomendamos "
"proveedores premium."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "¿No tienes proveedor de Usenet? Nosotros recomendamos probar %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5475,6 +5475,17 @@ msgstr "Error al recuperar la URL; %s"
#~ "Lanzar mi navegador de interner con la página de SABnzbd al arrancar el "
#~ "programa."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "Para poder descargar de Usenet, necesitas acceso con un proveedor. Tu "
#~ "proveedor de acceso a internet te lo puede dar, aunque recomendamos "
#~ "proveedores premium."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "¿No tienes proveedor de Usenet? Nosotros recomendamos probar %s."
#~ msgid "Please enter a whole number."
#~ msgstr "Por favor introduzca un número completo."

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2017-06-22 07:07+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>\n"
"Language-Team: Finnish <fi@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:49+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -471,19 +471,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Ei voida lukea %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Virhe lisättäessä %s, poistetaan"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Virhe poistettaessa %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Ei voida lukea %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Virhe lisättäessä %s, poistetaan"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Vahdittua kansiota %s ei voida lukea"
@@ -1012,6 +1011,11 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Korjaaminen epäonnistui, ei tarpeeksi korjauslohkoja (%s puuttuu)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Noudetaan %s lohkoa..."
@@ -1020,10 +1024,6 @@ msgstr "Noudetaan %s lohkoa..."
msgid "Fetching"
msgstr "Noudetaan"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Korjaaminen epäonnistui, ei tarpeeksi korjauslohkoja (%s puuttuu)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1215,6 +1215,12 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr "Keskeytetään kaksoiskappale NZB \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Peruutettu, ei voi valmistua"
@@ -4565,10 +4571,6 @@ msgstr "Valitettavasti emme ymmärtäneet tuota. Yritä uudelleen."
msgid "Pause for..."
msgstr "Keskeytä ajaksi..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Päivitä"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Järjestä iän mukaan <small>Vanhin&rarr;Uusin</small>"
@@ -4625,6 +4627,10 @@ msgstr "Puhdistetaanko historia?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Ota JavaScript käyttöön, jotta Plush toimii oikein!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Päivitä"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Asetukset"
@@ -4931,15 +4937,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"Jotta voit ladata usenetistä tarvitset tarjoajan. Palveluntarjoajasi saattaa "
"tarjota sinulle sellaisen, mutta on suositeltavaa hankkia premium-tarjoaja."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Eikö sinulla ole usenet tarjoajaa? Suosittelemme kokeilemaan %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5238,6 +5239,9 @@ msgstr "Osoitteen nouto epäonnistui; %s"
#~ msgid "Processing Switches"
#~ msgstr "Käsittelyparametrit"
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Eikö sinulla ole usenet tarjoajaa? Suosittelemme kokeilemaan %s."
#~ msgid "Password protect access to SABnzbd (recommended)"
#~ msgstr "Suojaa SABnzbd käyttö salasanalla (suositeltavaa)"
@@ -5402,6 +5406,13 @@ msgstr "Osoitteen nouto epäonnistui; %s"
#~ msgid "Pause Interval"
#~ msgstr "Keskeytysväli"
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "Jotta voit ladata usenetistä tarvitset tarjoajan. Palveluntarjoajasi saattaa "
#~ "tarjota sinulle sellaisen, mutta on suositeltavaa hankkia premium-tarjoaja."
#~ msgid ""
#~ "After SABnzbd has finished restarting you will be able to access it at the "
#~ "following location: %s"

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2017-08-21 19:45+0000\n"
"Last-Translator: Fred <88com88@gmail.com>\n"
"Language-Team: French <fr@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:49+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -495,19 +495,18 @@ msgstr ""
"temps de post-traitement. Fonctionne uniquement pour les tâches qui ne "
"nécessitent aucune réparation."
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Impossible de lire %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Erreur lors de l'ajout de %s, suppression"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Erreur lors de la suppression de %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Impossible de lire %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Erreur lors de l'ajout de %s, suppression"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Impossible de lire le dossier surveillé %s"
@@ -1054,6 +1053,11 @@ msgid ""
msgstr ""
"Paramètres ou fichiers PAR2 non valides, impossible de vérifier ou réparer."
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Échec de la réparation, pas assez de blocs de réparation (manque %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Récupération de %s blocs..."
@@ -1062,10 +1066,6 @@ msgstr "Récupération de %s blocs..."
msgid "Fetching"
msgstr "Récupération en cours"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Échec de la réparation, pas assez de blocs de réparation (manque %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1262,6 +1262,14 @@ msgstr "Dupliquer NZB"
msgid "Pausing duplicate NZB \"%s\""
msgstr "Mise en pause du doublon NZB \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
"%d fichiers avec des noms en double ont été rejetés pour \"%s\". Cochez "
"\"allow_duplicate_files\" pour autoriser les noms de fichiers en double."
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Interrompu, ne peut être achevé"
@@ -4672,10 +4680,6 @@ msgstr "Désolé, nous n'avons pas pu interpréter ça. Essayez encore."
msgid "Pause for..."
msgstr "Mettre en pause pour…"
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Rafraîchir"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Trier par Age <small>Moins récent&rarr;Plus récent</small>"
@@ -4732,6 +4736,10 @@ msgstr "Vider l'historique ?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Vous devez activer JavaScript pour que Plush puisse fonctionner !"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Rafraîchir"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Options"
@@ -5040,17 +5048,14 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"Pour pouvoir télécharger sur les newsgroups, il est nécessaire d'avoir un "
"fournisseur usenet. Votre FAI peut vous fournir un accès, cependant un "
"fournisseur usenet premium est recommandé."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr ""
"Vous n'avez pas de fournisseur usenet? Nous vous recommendons d'essayer %s."
"Vous aurez besoin d'un accès pour télécharger à partir de Usenet. Votre FAI "
"peut vous en proposer un, mais il est recommandé de faire appel à un "
"fournisseur premium. Vous n'avez pas de fournisseur Usenet ? Nous vous "
"recommandons d'essayer %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5410,6 +5415,18 @@ msgstr "Échec de récupération de l'URL ; %s"
#~ "<strong>Forcer téléchargements</strong> pour télécharger de suite tous les "
#~ "NZB correspondants."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "Pour pouvoir télécharger sur les newsgroups, il est nécessaire d'avoir un "
#~ "fournisseur usenet. Votre FAI peut vous fournir un accès, cependant un "
#~ "fournisseur usenet premium est recommandé."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr ""
#~ "Vous n'avez pas de fournisseur usenet? Nous vous recommendons d'essayer %s."
#~ msgid "Enable HTTPS access to SABnzbd."
#~ msgstr "Activer l'accès à SABnzbd via HTTPS."

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"PO-Revision-Date: 2017-09-01 23:03+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2017-08-21 19:38+0000\n"
"Last-Translator: ION IL <Unknown>\n"
"Language-Team: Hebrew <he@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:49+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -467,19 +467,18 @@ msgstr ""
".עבודות יתחילו להיפרק במהלך ההורדה כדי להפחית זמן לאחר-עיבוד. עובד רק עבור "
"עבודות שאינן צריכות תיקון"
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "לא יכול לקרוא את %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "מסיר ,%s שגיאה בזמן הוספת"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "%s שגיאה בהסרת"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "לא יכול לקרוא את %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "מסיר ,%s שגיאה בזמן הוספת"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "%s לא ניתן לקרוא את התיקייה המושגחת"
@@ -1005,6 +1004,11 @@ msgid ""
msgstr ""
"בלתי תקפים, לא יכול לוודא או לתקן PAR2 בלתי תקפים או פרמטרי par2 קבצי"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "(קצר %s) תיקון נכשל, אין מספיק גושי תיקון"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "...מושך %s גושים"
@@ -1013,10 +1017,6 @@ msgstr "...מושך %s גושים"
msgid "Fetching"
msgstr "מושך"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "(קצר %s) תיקון נכשל, אין מספיק גושי תיקון"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1208,6 +1208,14 @@ msgstr "NZB שכפל"
msgid "Pausing duplicate NZB \"%s\""
msgstr "\"%s\" NZB משהה שכפול"
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
".כדי להתיר שמות כפולים של קבצים \"allow_duplicate_files\" אפשר את .\"%s\" "
"הושלכו %d קבצים עם שמות כפולים עבור"
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "בוטל, לא יכול להיות שלם"
@@ -3254,7 +3262,7 @@ msgstr "חלופי NZB בכישלון, נסה"
#: sabnzbd/skintext.py
msgid "Some servers provide an alternative NZB when a download fails."
msgstr ".חלופי כאשר הורדה נכשלת NZB מספר שרתים מספקים"
msgstr ".חלופי כאשר הורדת נכשלת NZB מספר שרתים מספקים"
#: sabnzbd/skintext.py
msgid "Use tags from indexer"
@@ -3265,8 +3273,8 @@ msgid ""
"When sorting, use tags from indexer for title, season, episode, etc. "
"Otherwise all naming is derived from the NZB name."
msgstr ""
".בעת מיון, השתמש בתגים ממדדן עבור כותרת, עונה, פרק וכדומה\r\n"
".NZB-אחרת כל מתן השמות נגזר משם ה"
".NZB-בעת מיון, השתמש בתגים ממדדן עבור כותרת, עונה, פרק וכדומה. אחרת כל מתן "
"השמות נגזר משם ה"
#: sabnzbd/skintext.py
msgid "Enable folder rename"
@@ -3502,7 +3510,7 @@ msgstr "אפשר סינון"
#: sabnzbd/skintext.py
msgid "Action downloads according to filtering rules."
msgstr ".הורדה בהתאם לכללי הסינון"
msgstr ".הורדות פעולה בהתאם לחוקי הסינון"
#: sabnzbd/skintext.py
msgid "Abort If"
@@ -4521,10 +4529,6 @@ msgstr ".סליחה, לא יכולנו לפרש את זה. נסה שוב"
msgid "Pause for..."
msgstr "...השהה למשך"
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "רענן"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "מיין לפי גיל <small>החדש ביותר&rarr;הישן ביותר</small>"
@@ -4581,6 +4585,10 @@ msgstr "?לטהר את ההיסטוריה"
msgid "You must enable JavaScript for Plush to function!"
msgstr "!יתפקד Plush-כדי ש JavaScript אתה חייב לאפשר"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "רענן"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "אפשרויות"
@@ -4883,15 +4891,12 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
".תידרש לך גישה אל ספק. ספק שירותי האינטרנט שלך עשוי לספק לך גישה, אולם מומלץ "
"ספק פרימיום usenet-על מנת להוריד מ"
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr ".%s אנו ממליצים לנסות את ?usenet אין לך ספק"
".%s אנחנו ממליצים לנסות את ?Usenet נדרשת גישה אל ספק. ספק האינטרנט שלך עשוי "
"לספק לך גישה, אמנם ספק פרימיום הוא מומלץ. אין לך ספק Usenet-על מנת להוריד מ"
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"PO-Revision-Date: 2017-09-03 14:07+0000\n"
"Last-Translator: Steffen Bærø <steffen.baro@gmail.com>\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2017-05-23 11:46+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>\n"
"Language-Team: Norwegian Bokmal <nb@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-04 05:51+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -47,11 +47,11 @@ msgstr "par2-binærfil... IKKE funnet!"
#: SABnzbd.py [Error message] # SABnzbd.py [Error message]
msgid "Verification and repair will not be possible."
msgstr "Verifikasjon og reparasjon vil ikke være mulig."
msgstr ""
#: SABnzbd.py [Error message]
msgid "MultiPar binary... NOT found!"
msgstr "MultiPar-binærfil... IKKE funnet!"
msgstr ""
#: SABnzbd.py [Warning message]
msgid "Your UNRAR version is %s, we recommend version %s or higher.<br />"
@@ -60,7 +60,7 @@ msgstr ""
#: SABnzbd.py [Error message]
msgid "Downloads will not unpacked."
msgstr "Nedlastinger vil ikke blir pakket ut."
msgstr ""
#: SABnzbd.py [Error message]
msgid "unrar binary... NOT found"
@@ -467,19 +467,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Kan ikke lese %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Kunne ikke legge til %s, tar bort"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Feil ved fjerning av %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Kan ikke lese %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Kunne ikke legge til %s, tar bort"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Kan ikke lese den overvåkede mappen %s"
@@ -1007,6 +1006,12 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Mislykket reparasjon, finner ikke nødvendige reparasjonsblokker (%s mangler)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Henter %s blokker..."
@@ -1015,11 +1020,6 @@ msgstr "Henter %s blokker..."
msgid "Fetching"
msgstr "Henter"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Mislykket reparasjon, finner ikke nødvendige reparasjonsblokker (%s mangler)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1209,6 +1209,12 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr "Stanser duplikatfil \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Avbrutt, kan ikke fullføres"
@@ -4517,10 +4523,6 @@ msgstr "Beklager, vi kunne ikke finne ut av det. Prøv igjen."
msgid "Pause for..."
msgstr "Pause i..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Oppdatere"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Sorter etter alder <small>Eldst&rarr;Ny</small>"
@@ -4577,6 +4579,10 @@ msgstr "Vil du virkelig slette historikken?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Du må aktivere Javaskript for at Plush skal fungere!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Oppdatere"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Alternativer"
@@ -4882,16 +4888,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"For å laste ned fra usenet trenger du tilgang til en leverandør. Din "
"internettleverandør kan gi deg tilgang, men en \"proff\" leverandør "
"anbefales."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Har du ikke noen usenet leverandør? Vi anbefaler å prøve %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5256,6 +5256,17 @@ msgstr "URL henting mislyktes; %s"
#~ "Launch my internet browser with the SABnzbd page when the program starts."
#~ msgstr "Start webleseren med SABnzbd's side når programmet startes."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "For å laste ned fra usenet trenger du tilgang til en leverandør. Din "
#~ "internettleverandør kan gi deg tilgang, men en \"proff\" leverandør "
#~ "anbefales."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Har du ikke noen usenet leverandør? Vi anbefaler å prøve %s."
#~ msgid "This field is required."
#~ msgstr "Detta feltet kreves."

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"PO-Revision-Date: 2017-09-04 13:02+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2017-08-22 21:06+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>\n"
"Language-Team: Dutch <nl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-05 04:59+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -102,7 +102,7 @@ msgstr "HTTPS is uitgeschakeld vanwege ontbrekende CERT- en KEY-bestanden"
#: SABnzbd.py [Error message]
msgid "Failed to start web-interface: "
msgstr "Webinterface kon niet gestart worden: "
msgstr "Gebruikersinterface kon niet gestart worden: "
#: SABnzbd.py [Error message]
msgid "Cannot reach the SABHelper service"
@@ -488,19 +488,18 @@ msgstr ""
"verkort de tijd die nodig is voor het nabewerken. Dit werkt alleen als de "
"download niet beschadigd is."
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "%s kan niet gelezen worden"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Fout bij toevoegen van %s, wordt weer verwijderd"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Fout bij verwijderen van %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "%s kan niet gelezen worden"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Fout bij toevoegen van %s, wordt weer verwijderd"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Bewaakte map %s kan niet gelezen worden"
@@ -1035,18 +1034,19 @@ msgstr ""
"Ongeldige par2 bestanden of ongeldige Par2 parameters, kan niet verifiëren "
"en repareren."
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Reparatie mislukt, te weinig herstelblokken (%s tekort)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "%s herstelblokken downloaden..."
msgstr "Ophalen van %s blokken..."
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching"
msgstr "Ophalen"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Reparatie mislukt, te weinig herstelblokken (%s tekort)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1054,7 +1054,7 @@ msgstr "Repareren"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "[%s] Repaired in %s"
msgstr "[%s] Gerepareerd in %s"
msgstr "[%s] Reparatie in %s"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py
@@ -1239,6 +1239,15 @@ msgstr "Dubbele download"
msgid "Pausing duplicate NZB \"%s\""
msgstr "Dubbele download \"%s\" gepauzeerd"
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
"Er zijn %d bestanden met dezelfde bestandsnaam niet toegevoegd aan download "
"\"%s\". Zet \"allow_duplicate_files\" aan om dubbele bestandsnamen toe te "
"staan."
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Afgebroken, kan niet voltooid worden"
@@ -1277,7 +1286,7 @@ msgstr "VERSPREIDINGSWACHTTIJD %s min"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Gedownload in %s met een gemiddelde snelheid van %sB/s"
msgstr "Gedownload in %s met een gemiddelde van %sB/sec"
#: sabnzbd/nzbstuff.py # sabnzbd/skintext.py [Job details page, file age column header] # sabnzbd/skintext.py
msgid "Age"
@@ -1376,7 +1385,7 @@ msgstr "Probleemoplosser"
#: sabnzbd/sabtraylinux.py # sabnzbd/sabtraylinux.py
#: sabnzbd/skintext.py [#: Config->Scheduler] # sabnzbd/skintext.py # sabnzbd/skintext.py [Config->Scheduling]
msgid "Restart"
msgstr "Herstart"
msgstr "Herstarten"
#: sabnzbd/osxmenu.py # sabnzbd/sabtray.py
msgid "Restart without login"
@@ -1426,8 +1435,8 @@ msgid ""
" Please restart SABnzbd with a different port number."
msgstr ""
"\n"
" SABnzbd heeft een vrije TCP/IP poort nodig voor de interne "
"webserver.<br>\n"
" SABnzbd heeft een vrije TCP/IP poort nodig voor de interne web "
"server.<br>\n"
"Poort %s op %s is geprobeerd, maar is niet beschikbaar.<br>\n"
"Een ander programma gebruikt de poort al of SABnzbd is al actief.<br>\n"
"<br>\n"
@@ -1448,7 +1457,7 @@ msgid ""
" Please restart SABnzbd with a proper host address."
msgstr ""
"\n"
" SABnzbd heeft een geldig host adres voor de interne webserver.<br>\n"
" SABnzbd heeft een geldig host adres voor de interne web server.<br>\n"
"Je hebt een onbruikbaar adres opgegeven.<br>\n"
"Veilige waarden zijn <b>localhost</b> en <b>0.0.0.0</b><br>\n"
"<br>\n"
@@ -1737,7 +1746,7 @@ msgstr "Lege RSS-feed gevonden (%s)"
#: sabnzbd/sabtray.py # sabnzbd/sabtraylinux.py
msgid "Show interface"
msgstr "Toon webinterface"
msgstr "Toon Interface"
#: sabnzbd/sabtray.py # sabnzbd/sabtraylinux.py
msgid "Open complete folder"
@@ -1815,7 +1824,7 @@ msgstr "Script uitvoeren..."
#: sabnzbd/skintext.py [PP status]
msgid "Fetching extra blocks..."
msgstr "Extra herstelblokken downloaden..."
msgstr "Extra blokken ophalen..."
#: sabnzbd/skintext.py [PP status]
msgid "Quick Check..."
@@ -2730,7 +2739,7 @@ msgstr "Wijzigingen worden pas actief na herstart!"
#: sabnzbd/skintext.py
msgid "SABnzbd Web Server"
msgstr "Webserver"
msgstr "Web server"
#: sabnzbd/skintext.py
msgid "SABnzbd Host"
@@ -2750,7 +2759,7 @@ msgstr "Poort waar op SABnzbd luistert."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Web Interface"
msgstr "Webinterface"
msgstr "Gebruikersinterface"
#: sabnzbd/skintext.py
msgid "Choose a skin."
@@ -2845,11 +2854,11 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Maximum line speed"
msgstr "Maximale snelheid internetverbinding"
msgstr "Maximale snelheid internet verbinding"
#: sabnzbd/skintext.py
msgid "Percentage of line speed"
msgstr "Percentage van snelheid internetverbinding"
msgstr "Percentage van snelheid internet verbinding"
#: sabnzbd/skintext.py
msgid "Which percentage of the linespeed should SABnzbd use, e.g. 50"
@@ -3005,7 +3014,7 @@ msgstr "Volledige API"
#: sabnzbd/skintext.py
msgid "Full Web interface"
msgstr "Volledig webinterface"
msgstr "Volledig web interface"
#: sabnzbd/skintext.py
msgid "Only external access requires login"
@@ -3448,7 +3457,7 @@ msgstr "Onderbreek downloaden tijdens nabewerken."
#: sabnzbd/skintext.py
msgid "Ignore Samples"
msgstr "Negeer samples"
msgstr "Verwerking van \"sample\"-bestanden"
#: sabnzbd/skintext.py
msgid "Filter out sample files (e.g. video samples)."
@@ -3625,7 +3634,7 @@ msgstr "Balanceren van server belasting"
#: sabnzbd/skintext.py
msgid "Prevent load-balancing"
msgstr "Schakel balancering uit"
msgstr "Schaken balancering uit"
#: sabnzbd/skintext.py
msgid "Allow load-balancing"
@@ -4435,7 +4444,7 @@ msgstr "Start SABnzbd opnieuw"
#: sabnzbd/skintext.py
msgid "Status and interface options"
msgstr "Status en webinterface opties"
msgstr "Status en interface opties"
#: sabnzbd/skintext.py
msgid "Or drag and drop files in the window!"
@@ -4613,10 +4622,6 @@ msgstr "Sorry, het opgegeven kunnen wij niet verwerken. Probeer nogmaals."
msgid "Pause for..."
msgstr "Pauzeer..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Ververs"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Sorteer op Leeftijd <small>Oud&rarr;Nieuw</small>"
@@ -4673,6 +4678,10 @@ msgstr "Wis de volledige geschiedenis?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "JavaScript is nodig voor de werking van Plush!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Ververs"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Opties"
@@ -4941,7 +4950,7 @@ msgid ""
"It is recommended you right click and bookmark this location and use this "
"bookmark to access SABnzbd when it is running in the background."
msgstr ""
"Tip: maak een \"Bladwijzer\" of \"Favoriet\" voor deze locatie, zodat je "
"Tip: maak een \"Bladwijzer\" of \"Favoriet\" voor deze lokatie, zodat je "
"SABnzbd gemakkelijk terug kunt vinden."
#: sabnzbd/skintext.py [Will be appended with a wiki-link, adjust word order accordingly]
@@ -4978,15 +4987,13 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"Om te kunnen downloaden van Usenet, heb je een provider nodig. Je Internet "
"bedrijf heeft misschien een server, maar we bevelen een betaalde server aan."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Heb je nog geen Usenet provider? Wij bevelen %s aan."
"Om te kunnen downloaden van Usenet, heb je een provider nodig. Het kan dat "
"je internet provider dit aanbiedt, maar we bevelen een betaalde server aan. "
"Heb je nog geen Usenet provider? Wij bevelen %s aan."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5312,6 +5319,16 @@ msgstr "URL ophalen mislukt; %s"
#~ "Launch my internet browser with the SABnzbd page when the program starts."
#~ msgstr "Start de web browser wanneer SABnzbd opstart."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "Om te kunnen downloaden van Usenet, heb je een provider nodig. Je Internet "
#~ "bedrijf heeft misschien een server, maar we bevelen een betaalde server aan."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Heb je nog geen Usenet provider? Wij bevelen %s aan."
#~ msgid "This field is required."
#~ msgstr "Verplicht veld"

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2015-12-28 10:22+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>\n"
"Language-Team: Polish <pl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:49+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -467,19 +467,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Nie można odczytać %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Błąd podczas dodawania %s, usuwanie"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Błąd podczas usuwania %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Nie można odczytać %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Błąd podczas dodawania %s, usuwanie"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Nie można odczytać obserwowanego katalogu %s"
@@ -1011,6 +1010,13 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Naprawa nie powiodła się, brak wystarczającej ilości bloków naprawczych "
"(brakuje %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Pobieranie %s bloków..."
@@ -1019,12 +1025,6 @@ msgstr "Pobieranie %s bloków..."
msgid "Fetching"
msgstr "Pobieranie"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Naprawa nie powiodła się, brak wystarczającej ilości bloków naprawczych "
"(brakuje %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1216,6 +1216,12 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr "Wstrzymuję zduplikowany NZB \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Przerwano, nie można ukończyć"
@@ -4535,10 +4541,6 @@ msgstr "Przykro mi, nie rozumiem. Spróbuj ponownie."
msgid "Pause for..."
msgstr "Wstrzymaj na..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Odśwież"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Sortuj według wieku <small>Najstarsze&rarr;Najnowsze</small>"
@@ -4595,6 +4597,10 @@ msgstr "Wyczyścić historię?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Plush wymaga włączenia obsługi JavaScript!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Odśwież"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Opcje"
@@ -4899,15 +4905,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"Pobieranie z Usenetu wymaga dostępu do dostawcy. Twój ISP może umożliwiać "
"dostęp, aczkolwiek zalecany jest dostawca klasy premium."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Nie masz dostawcy Usenet? Polecamy spróbować %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5470,6 +5471,16 @@ msgstr "Pobieranie URL nie powiodło się; %s"
#~ "Launch my internet browser with the SABnzbd page when the program starts."
#~ msgstr "Uruchom stronę SABnzbd w przeglądarce podczas uruchamiania programu"
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "Pobieranie z Usenetu wymaga dostępu do dostawcy. Twój ISP może umożliwiać "
#~ "dostęp, aczkolwiek zalecany jest dostawca klasy premium."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Nie masz dostawcy Usenet? Polecamy spróbować %s."
#~ msgid "This field is required."
#~ msgstr "To pole jest wymagane"

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2016-01-01 22:58+0000\n"
"Last-Translator: lrrosa <Unknown>\n"
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:50+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:50+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -469,19 +469,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Não é possível ler %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Erro ao adicionar %s. Removendo"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Erro ao remover %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Não é possível ler %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Erro ao adicionar %s. Removendo"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Não é possível ler a Pasta de Assistidos %s"
@@ -1010,6 +1009,11 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Reparação falhou. Blocos de reparação insuficientes (faltam %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Obtendo %s blocos..."
@@ -1018,10 +1022,6 @@ msgstr "Obtendo %s blocos..."
msgid "Fetching"
msgstr "Obtendo"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Reparação falhou. Blocos de reparação insuficientes (faltam %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1213,6 +1213,12 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausando NZB duplicado \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Cancelado, não é possível concluir"
@@ -4533,10 +4539,6 @@ msgstr "Perdão, não conseguimos interpretar isso. Tente novamente."
msgid "Pause for..."
msgstr "Pausar por..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Atualizar"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Ordenar por Idade <small>Mais antigo&rarr;Mais novo</small>"
@@ -4593,6 +4595,10 @@ msgstr "Limpar o Histórico?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Você deve habilitar o JavaScript para Plush funcionar!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Atualizar"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Opções"
@@ -4899,16 +4905,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"Para baixar a partir da usenet você precisa ter acesso a um provedor. Seu "
"provedor de Internet pode fornecer-lhe acesso, no entanto, um provedor "
"exclusivo é recomendado."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Não tem um provedor usenet? Recomendamos testar %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5400,6 +5400,17 @@ msgstr "A busca da URL falhou; %s"
#~ msgid "Misc"
#~ msgstr "Diversos"
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "Para baixar a partir da usenet você precisa ter acesso a um provedor. Seu "
#~ "provedor de Internet pode fornecer-lhe acesso, no entanto, um provedor "
#~ "exclusivo é recomendado."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Não tem um provedor usenet? Recomendamos testar %s."
#~ msgid "This field is required."
#~ msgstr "Este campo é necessário."

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2016-07-29 16:20+0000\n"
"Last-Translator: nicusor <Unknown>\n"
"Language-Team: Romanian <ro@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:49+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -471,19 +471,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Nu pot citi %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Eroare adăugare %s, ştergem"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Eroare ştergere %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Nu pot citi %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Eroare adăugare %s, ştergem"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Nu pot citi Dosar Urnărire %s"
@@ -1014,6 +1013,11 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Reparare nereuşită, blocuri reparare insuficiente (%s mai puţin)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Descărcare %s blocuri..."
@@ -1022,10 +1026,6 @@ msgstr "Descărcare %s blocuri..."
msgid "Fetching"
msgstr "Descărcare"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Reparare nereuşită, blocuri reparare insuficiente (%s mai puţin)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1218,6 +1218,12 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr "Întrerupem duplicat NZB \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Anulat nu poate fi finalizat"
@@ -4538,10 +4544,6 @@ msgstr "Ne pare rău, nu am putut interpreta informațiile. Încearcă din nou."
msgid "Pause for..."
msgstr "Pauză timp de..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Reîmprospătează"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Sortează după Vârstă <small>Cel mai Vechi&rarr;Cel mai Nou</small>"
@@ -4598,6 +4600,10 @@ msgstr "Goliţi Istoricul?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Trebuie să activaţi JavaScript pentru ca Plush să funcţioneze!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Reîmprospătează"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Opțiuni"
@@ -4905,15 +4911,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"Pentru a descărca de pe usenet veţi avea nevoie de un furnizor. ISP-ul dvs. "
"vă poate oferi acces, totuşi un furnizor premium e recomandat."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Nu aveţi un furnizor usenet? Vă recomandăm să încercaţi %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5386,6 +5387,13 @@ msgstr "Descărcare URL nereuşită; %s"
#~ msgid "Please enter a whole number."
#~ msgstr "Vă rugăm să introduceţi un număr întreg."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "Pentru a descărca de pe usenet veţi avea nevoie de un furnizor. ISP-ul dvs. "
#~ "vă poate oferi acces, totuşi un furnizor premium e recomandat."
#~ msgid "This field is required."
#~ msgstr "Acest câmp este obligatoriu."
@@ -5438,6 +5446,9 @@ msgstr "Descărcare URL nereuşită; %s"
#~ msgid "Email Account Settings"
#~ msgstr "Setări Cont Email"
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Nu aveţi un furnizor usenet? Vă recomandăm să încercaţi %s."
#~ msgid "Skip par2 checking when files are 100% valid."
#~ msgstr "Ignoră verificarea par2 când fişierele sunt complete 100%%."

View File

@@ -2,14 +2,14 @@ msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-0.7.x\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2013-05-05 14:50+0000\n"
"Last-Translator: Pavel Maryanov <Unknown>\n"
"Language-Team: Russian <gmu@mx.ru>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:49+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
"Generated-By: pygettext.py 1.5\n"
@@ -460,19 +460,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Не удаётся прочитать %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Не удалось добавить %s: удалён"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Ошибка удаления %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Не удаётся прочитать %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Не удалось добавить %s: удалён"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Не удаётся прочитать наблюдаемую папку %s"
@@ -1001,6 +1000,12 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Ошибка исправления: недостаточно блоков восстановления (не хватает %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "загрузка %s блоков..."
@@ -1009,11 +1014,6 @@ msgstr "загрузка %s блоков..."
msgid "Fetching"
msgstr "Загрузка"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Ошибка исправления: недостаточно блоков восстановления (не хватает %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1203,6 +1203,12 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr "Приостановлен повторяющийся NZB-файл «%s»"
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr ""
@@ -4514,10 +4520,6 @@ msgstr ""
msgid "Pause for..."
msgstr "Приостановить на..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Обновить"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Сортировать по возрасту <small>от старых к новым</small>"
@@ -4574,6 +4576,10 @@ msgstr "Удалить историю?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Для работы Plush необходимо включить JavaScript"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Обновить"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Параметры"
@@ -4882,17 +4888,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"Для загрузки из сети Usenet требуется доступ к этой сети через "
"соответствующего поставщика услуг. Ваш поставщик услуг Интернета может "
"предоставить вам такой доступ, однако рекомендуется использовать usenet-"
"провайдеров класса Premium."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "У вас ещё нет поставщика услуг Usenet? Рекомендуем попробовать %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5415,6 +5414,18 @@ msgstr "Не удалось загрузить URL: %s"
#~ "Launch my internet browser with the SABnzbd page when the program starts."
#~ msgstr "Открывать веб-браузер со страницей SABnzbd при запуске программы."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "Для загрузки из сети Usenet требуется доступ к этой сети через "
#~ "соответствующего поставщика услуг. Ваш поставщик услуг Интернета может "
#~ "предоставить вам такой доступ, однако рекомендуется использовать usenet-"
#~ "провайдеров класса Premium."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "У вас ещё нет поставщика услуг Usenet? Рекомендуем попробовать %s."
#~ msgid "This field is required."
#~ msgstr "Это поле обязательно для заполнения."

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: ОZZII <ozzii.translate@gmail.com>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2015-12-28 10:25+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>\n"
"Language-Team: Serbian <sr@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:49+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:49+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -464,19 +464,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Неуспешно читање %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Грешка додавања %s, уклањање"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Greška pri uklanjanju %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Неуспешно читање %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Грешка додавања %s, уклањање"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Неуспешно читање надгледане фасцикле %s"
@@ -1003,6 +1002,11 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Погрешна поправка, нема довољно блокова за поправку (фали %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Учитавање %s блокова..."
@@ -1011,10 +1015,6 @@ msgstr "Учитавање %s блокова..."
msgid "Fetching"
msgstr "Добављам"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "Погрешна поправка, нема довољно блокова за поправку (фали %s)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1204,6 +1204,12 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr "Паузирам због дуплог NZB-а \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Поништено, не може да се заврши"
@@ -4501,10 +4507,6 @@ msgstr "Žao nam je, nismo mogli to da interpretiramo. Pokušajte ponovo."
msgid "Pause for..."
msgstr "Паузирај за..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Освежи"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Среди по старост <small>Старије&rarr;Новије</small>"
@@ -4561,6 +4563,10 @@ msgstr "Очисти хронологију?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "За функционисање Plush-а упалите JavaScript!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Освежи"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Опције"
@@ -4865,15 +4871,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"За преузимање са 'usenet' треба Вам приступ привајдеру. Можда Вам Ваш ISP "
"пружа приступ, било како премијум провајдер је препоручен."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Немате 'usenet' провајдер? Препоручујемо Вам %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -4937,6 +4938,9 @@ msgstr "Погрешно учитавање УРЛ-а; %s"
#~ msgid "This field is required."
#~ msgstr "Ово поље је обавезно."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Немате 'usenet' провајдер? Препоручујемо Вам %s."
#~ msgid "Access"
#~ msgstr "Приступ"
@@ -5383,6 +5387,13 @@ msgstr "Погрешно учитавање УРЛ-а; %s"
#~ "Launch my internet browser with the SABnzbd page when the program starts."
#~ msgstr "Покрени мој претраживач са SABnzbd листом при покретању."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "За преузимање са 'usenet' треба Вам приступ привајдеру. Можда Вам Ваш ISP "
#~ "пружа приступ, било како премијум провајдер је препоручен."
#~ msgid ""
#~ "After SABnzbd has finished restarting you will be able to access it at the "
#~ "following location: %s"

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2016-02-20 20:34+0000\n"
"Last-Translator: shypike <Unknown>\n"
"Language-Team: Swedish <sv@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:50+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:50+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -465,19 +465,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Kan ej läsa %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Det gick inte att lägga till %s, tar bort"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "Fel vid borttagning av %s"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "Kan ej läsa %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "Det gick inte att lägga till %s, tar bort"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "Kan ej läsa övervakad mapp %s"
@@ -1007,6 +1006,12 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Misslyckad reparation, finns ej tillräckligt med reparationsblock (%s saknas)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "Hämtar %s block..."
@@ -1015,11 +1020,6 @@ msgstr "Hämtar %s block..."
msgid "Fetching"
msgstr "Hämtar"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr ""
"Misslyckad reparation, finns ej tillräckligt med reparationsblock (%s saknas)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1209,6 +1209,12 @@ msgstr ""
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausar dubblett för NZB \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Avbrutet, kan inte slutföras"
@@ -4515,10 +4521,6 @@ msgstr "Tyvärr, vi kunde inte tolka det. Försök igen."
msgid "Pause for..."
msgstr "Pausa i..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "Uppdatera"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Sortera efter ålder <small>Äldst&rarr;Nyast</small>"
@@ -4575,6 +4577,10 @@ msgstr "Vill du verkligen tömma historiken?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "Du måste aktivera JavaScript för Plush ska fungera!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "Uppdatera"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "Alternativ"
@@ -4883,16 +4889,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
"För att ladda ner från usenet du behöver tillgång till en leverantör. Din "
"internetleverantör kan ge dig tillgång, men en premie leverantör "
"rekommenderas."
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "Har du inte någon usenet leverantör? Vi rekommenderar att prova %s."
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5286,6 +5286,17 @@ msgstr "URL hämtning misslyckades; %s"
#~ "Launch my internet browser with the SABnzbd page when the program starts."
#~ msgstr "Starta webbläsaren med SABnzbd's sida när programet startas."
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr ""
#~ "För att ladda ner från usenet du behöver tillgång till en leverantör. Din "
#~ "internetleverantör kan ge dig tillgång, men en premie leverantör "
#~ "rekommenderas."
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "Har du inte någon usenet leverantör? Vi rekommenderar att prova %s."
#~ msgid "This field is required."
#~ msgstr "Detta fält krävs."

View File

@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-09-02 13:51+0000\n"
"POT-Creation-Date: 2017-08-24 15:30+0000\n"
"PO-Revision-Date: 2017-06-22 07:06+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-09-03 05:50+0000\n"
"X-Launchpad-Export-Date: 2017-08-25 05:50+0000\n"
"X-Generator: Launchpad (build 18446)\n"
#: SABnzbd.py [Error message]
@@ -459,19 +459,18 @@ msgid ""
"time. Only works for jobs that do not need repair."
msgstr ""
#: sabnzbd/dirscanner.py # sabnzbd/dirscanner.py # sabnzbd/dirscanner.py
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "无法读取 %s"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "加载 %s 出错,正在移除"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Error removing %s"
msgstr "移除 %s 时出错"
#: sabnzbd/dirscanner.py [Warning message] # sabnzbd/rss.py [Warning message]
msgid "Cannot read %s"
msgstr "无法读取 %s"
#: sabnzbd/dirscanner.py [Error message]
msgid "Error while adding %s, removing"
msgstr "加载 %s 出错,正在移除"
#: sabnzbd/dirscanner.py [Error message] # sabnzbd/dirscanner.py [Error message]
msgid "Cannot read Watched Folder %s"
msgstr "无法读取监视文件夹 %s"
@@ -988,6 +987,11 @@ msgid ""
"Invalid par2 files or invalid PAR2 parameters, cannot verify or repair"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "修复失败,修复块不足 (缺 %s 块)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Fetching %s blocks..."
msgstr "正在装取 %s 块..."
@@ -996,10 +1000,6 @@ msgstr "正在装取 %s 块..."
msgid "Fetching"
msgstr "正在装取"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repair failed, not enough repair blocks (%s short)"
msgstr "修复失败,修复块不足 (缺 %s 块)"
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Repairing"
@@ -1189,6 +1189,12 @@ msgstr "重复的 NZB 文件"
msgid "Pausing duplicate NZB \"%s\""
msgstr "正在暂停重复 NZB \"%s\""
#: sabnzbd/nzbstuff.py [Warning message]
msgid ""
"%d files with duplicate filenames were discared for \"%s\". Enable "
"\"allow_duplicate_files\" to allow duplicate filenames."
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "已中止,无法完成"
@@ -4441,10 +4447,6 @@ msgstr "抱歉,无法理解您的输入。请重试。"
msgid "Pause for..."
msgstr "暂停..."
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Refresh"
msgstr "刷新"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "按发布时间排序 <small>最早&rarr;最新</small>"
@@ -4501,6 +4503,10 @@ msgstr "清空历史?"
msgid "You must enable JavaScript for Plush to function!"
msgstr "您必须启用 JavaScript 才能使用 Plush 模板!"
#: sabnzbd/skintext.py
msgid "Refresh"
msgstr "刷新"
#: sabnzbd/skintext.py
msgid "Options"
msgstr "选项"
@@ -4801,13 +4807,10 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In order to download from usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended."
msgstr "要从 usenet 下载您需要有一家提供商的访问权限。您的 ISP 可能会为您提供权限,但推荐您选用付费的高级提供商。"
#: sabnzbd/skintext.py
msgid "Don't have a usenet provider? We recommend trying %s."
msgstr "还没有 usenet 提供商r? 我们推荐试试 %s。"
"In order to download from Usenet you will require access to a provider. Your "
"ISP may provide you with access, however a premium provider is recommended. "
"Don't have a Usenet provider? We recommend trying %s."
msgstr ""
#: sabnzbd/tvsort.py [Error message]
msgid "Error getting TV info (%s)"
@@ -5308,6 +5311,14 @@ msgstr "URL 装取失败; %s"
#~ "Launch my internet browser with the SABnzbd page when the program starts."
#~ msgstr "程序启动时启动互联网浏览器打开 SABnzbd 页面。"
#~ msgid ""
#~ "In order to download from usenet you will require access to a provider. Your "
#~ "ISP may provide you with access, however a premium provider is recommended."
#~ msgstr "要从 usenet 下载您需要有一家提供商的访问权限。您的 ISP 可能会为您提供权限,但推荐您选用付费的高级提供商。"
#~ msgid "Don't have a usenet provider? We recommend trying %s."
#~ msgstr "还没有 usenet 提供商r? 我们推荐试试 %s。"
#~ msgid "This field is required."
#~ msgstr "该字段必填。"

View File

@@ -105,7 +105,6 @@ from sabnzbd.bpsmeter import BPSMeter
import sabnzbd.cfg as cfg
import sabnzbd.database
import sabnzbd.lang as lang
import sabnzbd.par2file as par2file
import sabnzbd.api
import sabnzbd.directunpacker as directunpacker
from sabnzbd.decorators import synchronized, notify_downloader
@@ -320,17 +319,16 @@ def initialize(pause_downloader=False, clean_up=False, evalSched=False, repair=0
paused = BPSMeter.do.read()
PostProcessor()
NzbQueue()
Downloader(pause_downloader or paused)
Assembler()
PostProcessor()
NzbQueue.do.read_queue(repair)
Downloader(pause_downloader or paused)
DirScanner()
Rating()
@@ -537,7 +535,7 @@ def guard_https_ver():
set_https_verification(cfg.enable_https_verification())
def add_url(url, pp=None, script=None, cat=None, priority=None, nzbname=None, feed_name=None):
def add_url(url, pp=None, script=None, cat=None, priority=None, nzbname=None):
""" Add NZB based on a URL, attributes optional """
if 'http' not in url:
return
@@ -548,13 +546,7 @@ def add_url(url, pp=None, script=None, cat=None, priority=None, nzbname=None, fe
if cat and cat.lower() == 'default':
cat = None
logging.info('Fetching %s', url)
# Add feed name if it came from RSS
msg = T('Trying to fetch NZB from %s') % url
if feed_name:
msg = '%s - %s' % (feed_name, msg)
# Generate the placeholder
future_nzo = NzbQueue.do.generate_future(msg, pp, script, cat, url=url, priority=priority, nzbname=nzbname)
URLGrabber.do.add(url, future_nzo)
return future_nzo.nzo_id

View File

@@ -105,6 +105,9 @@ def api_handler(kwargs):
name = kwargs.get('name', '')
callback = kwargs.get('callback', '')
# Extend the timeout of API calls to 10minutes
cherrypy.response.timeout = 600
if isinstance(mode, list):
mode = mode[0]
if isinstance(output, list):
@@ -1342,6 +1345,7 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
slot['size'] = format_bytes(bytes)
slot['sizeleft'] = format_bytes(bytesleft)
slot['percentage'] = "%s" % (int(((mb - mbleft) / mb) * 100)) if mb != mbleft else '0'
slot['missing'] = pnfo.missing
slot['mbmissing'] = "%.2f" % (pnfo.bytes_missing / MEBI)
slot['direct_unpack'] = pnfo.direct_unpack
if not output:
@@ -1356,8 +1360,8 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
else:
slot['status'] = Status.DOWNLOADING
else:
# Ensure compatibility of API status
if status == Status.DELETED or priority == TOP_PRIORITY:
# ensure compatibility of API status
if status in (Status.DELETED, ):
status = Status.DOWNLOADING
slot['status'] = "%s" % (status)
@@ -1376,9 +1380,8 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
datestart = datetime.datetime.now()
slot['eta'] = 'unknown'
# Do not show age when it's not known
if average_date.year < 2000:
slot['avg_age'] = '-'
if status == Status.GRABBING:
slot['avg_age'] = '---'
else:
slot['avg_age'] = calc_age(average_date, bool(trans))
@@ -1459,7 +1462,8 @@ def rss_qstatus():
rss = RSS()
rss.channel.title = "SABnzbd Queue"
rss.channel.description = "Overview of current downloads"
rss.channel.link = "http://%s:%s%s/queue" % (cfg.cherryhost(), cfg.cherryport(), cfg.url_base())
rss.channel.link = "http://%s:%s/sabnzbd/queue" % (
cfg.cherryhost(), cfg.cherryport())
rss.channel.language = "en"
item = Item()
@@ -1490,7 +1494,7 @@ def rss_qstatus():
item = Item()
item.title = name
item.link = "http://%s:%s%s/history" % (cfg.cherryhost(), cfg.cherryport(), cfg.url_base())
item.link = "http://%s:%s/sabnzbd/history" % (cfg.cherryhost(), cfg.cherryport())
item.guid = nzo_id
status_line = []
status_line.append('<tr>')
@@ -1640,7 +1644,6 @@ def build_header(webdir='', output=None):
header['my_lcldata'] = sabnzbd.DIR_LCLDATA
header['my_home'] = sabnzbd.DIR_HOME
header['webdir'] = webdir or sabnzbd.WEB_DIR
header['url_base'] = cfg.url_base()
header['nt'] = sabnzbd.WIN32
header['darwin'] = sabnzbd.DARWIN

View File

@@ -22,6 +22,7 @@ sabnzbd.assembler - threaded assembly/decoding of files
import os
import Queue
import logging
import struct
import re
from threading import Thread
from time import sleep
@@ -29,14 +30,12 @@ import hashlib
import sabnzbd
from sabnzbd.misc import get_filepath, sanitize_filename, get_unique_filename, renamer, \
set_permissions, long_path, clip_path, has_win_device, get_all_passwords, diskspace, \
get_filename, get_ext
set_permissions, long_path, clip_path, has_win_device, get_all_passwords, diskspace
from sabnzbd.constants import Status, GIGI
import sabnzbd.cfg as cfg
from sabnzbd.articlecache import ArticleCache
from sabnzbd.postproc import PostProcessor
import sabnzbd.downloader
import sabnzbd.par2file as par2file
import sabnzbd.utils.rarfile as rarfile
from sabnzbd.encoding import unicoder, is_utf8
from sabnzbd.rating import Rating
@@ -90,7 +89,6 @@ class Assembler(Thread):
nzo.verify_nzf_filename(nzf)
nzf.filename = sanitize_filename(nzf.filename)
filepath = get_filepath(long_path(cfg.download_dir.get_path()), nzo, nzf.filename)
nzf.filename = get_filename(filepath)
if filepath:
logging.info('Decoding %s %s', filepath, nzf.type)
@@ -114,37 +112,41 @@ class Assembler(Thread):
# Clean-up admin data
nzf.remove_admin()
# Do rar-related processing
if rarfile.is_rarfile(filepath):
# Encryption and unwanted extension detection
rar_encrypted, unwanted_file = check_encrypted_and_unwanted_files(nzo, filepath)
if rar_encrypted:
if cfg.pause_on_pwrar() == 1:
logging.warning(remove_warning_label(T('WARNING: Paused job "%s" because of encrypted RAR file (if supplied, all passwords were tried)')), nzo.final_name)
nzo.pause()
else:
logging.warning(remove_warning_label(T('WARNING: Aborted job "%s" because of encrypted RAR file (if supplied, all passwords were tried)')), nzo.final_name)
nzo.fail_msg = T('Aborted, encryption detected')
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
# Parse par2 files
if nzf.is_par2:
# Always parse par2 files to get new md5of16k info
pack = self.parse_par2_file(nzf, filepath)
if pack and (nzo.md5packs.get(nzf.setname) is None):
nzo.md5packs[nzf.setname] = pack
logging.debug('Got md5pack for set %s', nzf.setname)
# Valid md5pack, so use this par2-file as main par2 file for the set
if nzf.setname in nzo.partable:
# First copy the set of extrapars, we need them later
nzf.extrapars = nzo.partable[nzf.setname].extrapars
nzo.partable[nzf.setname] = nzf
if unwanted_file:
logging.warning(remove_warning_label(T('WARNING: In "%s" unwanted extension in RAR file. Unwanted file is %s ')), nzo.final_name, unwanted_file)
logging.debug(T('Unwanted extension is in rar file %s'), filepath)
if cfg.action_on_unwanted_extensions() == 1 and nzo.unwanted_ext == 0:
logging.debug('Unwanted extension ... pausing')
nzo.unwanted_ext = 1
nzo.pause()
if cfg.action_on_unwanted_extensions() == 2:
logging.debug('Unwanted extension ... aborting')
nzo.fail_msg = T('Aborted, unwanted extension detected')
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
# Encryption and unwanted extension detection
rar_encrypted, unwanted_file = check_encrypted_and_unwanted_files(nzo, filepath)
if rar_encrypted:
if cfg.pause_on_pwrar() == 1:
logging.warning(remove_warning_label(T('WARNING: Paused job "%s" because of encrypted RAR file (if supplied, all passwords were tried)')), nzo.final_name)
nzo.pause()
else:
logging.warning(remove_warning_label(T('WARNING: Aborted job "%s" because of encrypted RAR file (if supplied, all passwords were tried)')), nzo.final_name)
nzo.fail_msg = T('Aborted, encryption detected')
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
# Add to direct unpack
nzo.add_to_direct_unpacker(nzf)
elif par2file.is_parfile(filepath):
# Parse par2 files, cloaked or not
nzo.handle_par2(nzf, filepath)
if unwanted_file:
logging.warning(remove_warning_label(T('WARNING: In "%s" unwanted extension in RAR file. Unwanted file is %s ')), nzo.final_name, unwanted_file)
logging.debug(T('Unwanted extension is in rar file %s'), filepath)
if cfg.action_on_unwanted_extensions() == 1 and nzo.unwanted_ext == 0:
logging.debug('Unwanted extension ... pausing')
nzo.unwanted_ext = 1
nzo.pause()
if cfg.action_on_unwanted_extensions() == 2:
logging.debug('Unwanted extension ... aborting')
nzo.fail_msg = T('Aborted, unwanted extension detected')
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
filter, reason = nzo_filtered_by_rating(nzo)
if filter == 1:
@@ -155,6 +157,9 @@ class Assembler(Thread):
nzo.fail_msg = T('Aborted, rating filter matched (%s)') % reason
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
if rarfile.is_rarfile(filepath):
nzo.add_to_direct_unpacker(nzf)
else:
sabnzbd.nzbqueue.NzbQueue.do.remove(nzo.nzo_id, add_to_history=False, cleanup=False)
PostProcessor.do.process(nzo)
@@ -191,6 +196,61 @@ class Assembler(Thread):
return path
def parse_par2_file(self, nzf, fname):
""" Get the hash table and the first-16k hash table from a PAR2 file
Return as dictionary, indexed on names or hashes for the first-16 table
For a full description of the par2 specification, visit:
http://parchive.sourceforge.net/docs/specifications/parity-volume-spec/article-spec.html
"""
table = {}
duplicates16k = []
try:
f = open(fname, 'rb')
except:
return table
try:
header = f.read(8)
while header:
name, hash, hash16k = parse_par2_file_packet(f, header)
if name:
table[name] = hash
if hash16k not in nzf.nzo.md5of16k:
nzf.nzo.md5of16k[hash16k] = name
elif nzf.nzo.md5of16k[hash16k] != name:
# Not unique and not already linked to this file
# Remove to avoid false-renames
duplicates16k.append(hash16k)
header = f.read(8)
except (struct.error, IndexError):
logging.info('Cannot use corrupt par2 file for QuickCheck, "%s"', fname)
table = {}
except:
logging.debug('QuickCheck parser crashed in file %s', fname)
logging.info('Traceback: ', exc_info=True)
table = {}
f.close()
# Have to remove duplicates at the end to make sure
# no trace is left in case of multi-duplicates
for hash16k in duplicates16k:
if hash16k in nzf.nzo.md5of16k:
old_name = nzf.nzo.md5of16k.pop(hash16k)
logging.debug('Par2-16k signature of %s not unique, discarding', old_name)
# If the filename was changed (duplicate filename) check if we already have the set
base_fname = os.path.split(fname)[1]
if table and base_fname != nzf.filename and table not in nzf.nzo.md5packs.values():
# Re-parse this par2 file to create new set
nzf.filename = base_fname
nzf.is_par2 = False
nzf.nzo.handle_par2(nzf, True)
return table
def file_has_articles(nzf):
""" Do a quick check to see if any articles are present for this file.
@@ -207,13 +267,55 @@ def file_has_articles(nzf):
return has
def parse_par2_file_packet(f, header):
""" Look up and analyze a FileDesc package """
nothing = None, None, None
if header != 'PAR2\0PKT':
return nothing
# Length must be multiple of 4 and at least 20
len = struct.unpack('<Q', f.read(8))[0]
if int(len / 4) * 4 != len or len < 20:
return nothing
# Next 16 bytes is md5sum of this packet
md5sum = f.read(16)
# Read and check the data
data = f.read(len - 32)
md5 = hashlib.md5()
md5.update(data)
if md5sum != md5.digest():
return nothing
# The FileDesc packet looks like:
# 16 : "PAR 2.0\0FileDesc"
# 16 : FileId
# 16 : Hash for full file **
# 16 : Hash for first 16K
# 8 : File length
# xx : Name (multiple of 4, padded with \0 if needed) **
# See if it's the right packet and get name + hash
for offset in range(0, len, 8):
if data[offset:offset + 16] == "PAR 2.0\0FileDesc":
hash = data[offset + 32:offset + 48]
hash16k = data[offset + 48:offset + 64]
filename = data[offset + 72:].strip('\0')
return filename, hash, hash16k
return nothing
RE_SUBS = re.compile(r'\W+sub|subs|subpack|subtitle|subtitles(?![a-z])', re.I)
def is_cloaked(nzo, path, names):
""" Return True if this is likely to be a cloaked encrypted post """
fname = unicoder(get_filename(path)).lower()
fname = unicoder(os.path.split(path)[1]).lower()
fname = os.path.splitext(fname)[0]
for name in names:
name = get_filename(name.lower())
name = os.path.split(name.lower())[1]
name, ext = os.path.splitext(unicoder(name))
if ext == u'.rar' and fname.startswith(name) and (len(fname) - len(name)) < 8 and len(names) < 3 and not RE_SUBS.search(fname):
# Only warn once
@@ -309,7 +411,7 @@ def check_encrypted_and_unwanted_files(nzo, filepath):
if cfg.unwanted_extensions() and cfg.action_on_unwanted_extensions():
for somefile in zf.namelist():
logging.debug('File contains: %s', somefile)
if get_ext(somefile).replace('.', '').lower() in cfg.unwanted_extensions():
if os.path.splitext(somefile)[1].replace('.', '').lower() in cfg.unwanted_extensions():
logging.debug('Unwanted file %s', somefile)
unwanted = somefile
zf.close()

View File

@@ -148,7 +148,6 @@ fail_hopeless_jobs = OptionBool('misc', 'fail_hopeless_jobs', True)
autodisconnect = OptionBool('misc', 'auto_disconnect', True)
no_dupes = OptionNumber('misc', 'no_dupes', 0)
no_series_dupes = OptionNumber('misc', 'no_series_dupes', 0)
series_propercheck = OptionBool('misc', 'series_propercheck', True)
pause_on_pwrar = OptionNumber('misc', 'pause_on_pwrar', 1)
ignore_samples = OptionBool('misc', 'ignore_samples', False)
auto_sort = OptionBool('misc', 'auto_sort', False)
@@ -253,7 +252,7 @@ keep_awake = OptionBool('misc', 'keep_awake', True)
win_menu = OptionBool('misc', 'win_menu', True)
allow_incomplete_nzb = OptionBool('misc', 'allow_incomplete_nzb', False)
enable_bonjour = OptionBool('misc', 'enable_bonjour', True)
reject_duplicate_files = OptionBool('misc', 'reject_duplicate_files', False)
allow_duplicate_files = OptionBool('misc', 'allow_duplicate_files', False)
max_art_opt = OptionBool('misc', 'max_art_opt', False)
use_pickle = OptionBool('misc', 'use_pickle', False)
ipv6_hosting = OptionBool('misc', 'ipv6_hosting', False)
@@ -275,7 +274,7 @@ history_limit = OptionNumber('misc', 'history_limit', 10, 0)
wait_ext_drive = OptionNumber('misc', 'wait_ext_drive', 5, 1, 60)
marker_file = OptionStr('misc', 'nomedia_marker', '')
ipv6_servers = OptionNumber('misc', 'ipv6_servers', 1, 0, 2)
url_base = OptionStr('misc', 'url_base', '/sabnzbd')
##############################################################################
# Config - Notifications

View File

@@ -398,6 +398,7 @@ class ConfigServer(object):
self.priority = OptionNumber(name, 'priority', 0, 0, 100, add=False)
# 'fillserver' field only here in order to set a proper priority when converting
self.fillserver = OptionBool(name, 'fillserver', False, add=False)
self.categories = OptionList(name, 'categories', default_val=['Default'], add=False)
self.notes = OptionStr(name, 'notes', '', add=False)
self.set_dict(values)
@@ -406,7 +407,7 @@ class ConfigServer(object):
def set_dict(self, values):
""" Set one or more fields, passed as dictionary """
for kw in ('displayname', 'host', 'port', 'timeout', 'username', 'password', 'connections', 'fillserver',
'ssl', 'ssl_verify', 'send_group', 'enable', 'optional', 'retention', 'priority', 'notes'):
'ssl', 'ssl_verify', 'send_group', 'enable', 'optional', 'retention', 'priority', 'categories', 'notes'):
try:
value = values[kw]
except KeyError:
@@ -437,6 +438,7 @@ class ConfigServer(object):
dict['retention'] = self.retention()
dict['send_group'] = self.send_group()
dict['priority'] = self.priority()
dict['categories'] = self.categories()
dict['notes'] = self.notes()
return dict

View File

@@ -27,7 +27,7 @@ REC_RAR_VERSION = 500
PNFO = namedtuple('PNFO', 'repair unpack delete script nzo_id filename password unpackstrht '
'msgid category url bytes_left bytes avg_stamp avg_date finished_files '
'active_files queued_files status priority bytes_missing direct_unpack')
'active_files queued_files status priority missing bytes_missing direct_unpack')
QNFO = namedtuple('QNFO', 'bytes bytes_left bytes_left_previous_page list q_size_list q_fullsize')

View File

@@ -74,8 +74,8 @@ class HistoryDB(object):
"""
# These class attributes will be accessed directly because
# they need to be shared by all instances
db_path = None # Will contain full path to history database
done_cleaning = False # Ensure we only do one Vacuum per session
db_path = None # Will contain full path to history database
done_cleaning = False # Ensure we only do one Vacuum per session
@synchronized(DB_LOCK)
def __init__(self):
@@ -85,6 +85,7 @@ class HistoryDB(object):
HistoryDB.db_path = os.path.join(sabnzbd.cfg.admin_dir.get_path(), DB_HISTORY_NAME)
self.connect()
def connect(self):
""" Create a connection to the database """
create_table = not os.path.exists(HistoryDB.db_path)
@@ -117,6 +118,7 @@ class HistoryDB(object):
_ = self.execute('PRAGMA user_version = 2;') and \
self.execute('ALTER TABLE "history" ADD COLUMN password TEXT;')
def execute(self, command, args=(), save=False):
''' Wrapper for executing SQL commands '''
for tries in xrange(5, 0, -1):
@@ -260,7 +262,7 @@ class HistoryDB(object):
if "d" in sabnzbd.cfg.history_retention():
# How many days to keep?
days_to_keep = int_conv(sabnzbd.cfg.history_retention().strip()[:-1])
seconds_to_keep = int(time.time()) - days_to_keep * 86400
seconds_to_keep = int(time.time()) - days_to_keep*3600*24
if days_to_keep > 0:
logging.info('Removing completed jobs older than %s days from history', days_to_keep)
return self.execute("""DELETE FROM history WHERE status = 'Completed' AND completed < ?""", (seconds_to_keep,), save=True)
@@ -271,6 +273,7 @@ class HistoryDB(object):
logging.info('Removing all but last %s completed jobs from history', to_keep)
return self.execute("""DELETE FROM history WHERE id NOT IN ( SELECT id FROM history WHERE status = 'Completed' ORDER BY completed DESC LIMIT ? )""", (to_keep,), save=True)
def add_history_db(self, nzo, storage, path, postproc_time, script_output, script_line):
""" Add a new job entry to the database """
t = build_history_info(nzo, storage, path, postproc_time, script_output, script_line)
@@ -290,7 +293,7 @@ class HistoryDB(object):
if categories:
categories = ['*' if c == 'Default' else c for c in categories]
post = " AND (CATEGORY = ?"
post += " OR CATEGORY = ? " * (len(categories) - 1)
post += " OR CATEGORY = ? " * (len(categories)-1)
post += ")"
command_args.extend(categories)
if failed_only:
@@ -515,6 +518,7 @@ def build_history_info(nzo, storage='', downpath='', postproc_time=0, script_out
fail_message, url_info, bytes, series, nzo.md5sum, password)
def unpack_history_info(item):
""" Expands the single line stage_log from the DB
into a python dictionary for use in the history display

View File

@@ -61,7 +61,7 @@ TIMER_LOCK = RLock()
class Server(object):
def __init__(self, id, displayname, host, port, timeout, threads, priority, ssl, ssl_verify, send_group, username=None,
password=None, optional=False, retention=0):
password=None, optional=False, retention=0, categories=None):
self.id = id
self.newid = None
@@ -81,6 +81,12 @@ class Server(object):
self.username = username
self.password = password
self.categories = categories
# Temporary deprication warning
if categories and (len(categories) > 1 or 'Default' not in categories):
logging.warning('[%s] Server specific categories option is scheduled to be removed in the next release of SABnzbd', self.host)
self.busy_threads = []
self.idle_threads = []
self.active = True
@@ -191,8 +197,6 @@ class Downloader(Thread):
self.write_fds = {}
self.servers = []
self.server_dict = {} # For faster lookups, but is not updated later!
self.server_nr = 0
self._timers = {}
for server in config.get_servers():
@@ -231,6 +235,7 @@ class Downloader(Thread):
username = srv.username()
password = srv.password()
optional = srv.optional()
categories = srv.categories()
retention = float(srv.retention() * 24 * 3600) # days ==> seconds
send_group = srv.send_group()
create = True
@@ -246,13 +251,8 @@ class Downloader(Thread):
break
if create and enabled and host and port and threads:
server = Server(newserver, displayname, host, port, timeout, threads, priority, ssl, ssl_verify,
send_group, username, password, optional, retention)
self.servers.append(server)
self.server_dict[newserver] = server
# Update server-count
self.server_nr = len(self.servers)
self.servers.append(Server(newserver, displayname, host, port, timeout, threads, priority, ssl, ssl_verify,
send_group, username, password, optional, retention, categories=categories))
return
@@ -645,8 +645,7 @@ class Downloader(Thread):
server.errormsg = errormsg
name = ' (%s)' % server.id
logging.warning(T('Probable account sharing') + name)
penalty = _PENALTY_SHARE
block = True
penalty = _PENALTY_SHARE
elif ecode in ('481', '482', '381') or (ecode == '502' and clues_login(msg)):
# Cannot login, block this server
if server.active:
@@ -681,8 +680,7 @@ class Downloader(Thread):
if server.errormsg != errormsg:
server.errormsg = errormsg
logging.warning(T('Cannot connect to server %s [%s]'), server.id, msg)
penalty = _PENALTY_UNKNOWN
block = True
penalty = _PENALTY_UNKNOWN
if block or (penalty and server.optional):
if server.active:
server.active = False
@@ -797,8 +795,8 @@ class Downloader(Thread):
# Remove this server from try_list
article.fetcher = None
# Allow all servers to iterate over each nzo/nzf again
sabnzbd.nzbqueue.NzbQueue.do.reset_try_lists(article.nzf, article.nzf.nzo)
# Allow all servers to iterate over each nzo/nzf again
sabnzbd.nzbqueue.NzbQueue.do.reset_try_lists(article.nzf, article.nzf.nzo)
if destroy:
nw.terminate(quit=quit)

View File

@@ -27,7 +27,6 @@ import urllib
import json
import re
import hashlib
import ssl
from threading import Thread
from random import randint
from xml.sax.saxutils import escape
@@ -55,6 +54,7 @@ from sabnzbd.nzbqueue import NzbQueue
import sabnzbd.wizard
from sabnzbd.utils.servertests import test_nntp_server_dict
from sabnzbd.decoder import HAVE_YENC, SABYENC_ENABLED
from sabnzbd.utils.sslinfo import ssl_version, ssl_protocols_labels
from sabnzbd.utils.diskspeed import diskspeedmeasure
from sabnzbd.utils.getperformance import getpystone
@@ -125,8 +125,8 @@ def Raiser(root='', **kwargs):
# Add extras
if args:
root = '%s?%s' % (root, urllib.urlencode(args))
# Optionally add the leading /sabnzbd/ (or what the user set)
if not root.startswith(cfg.url_base()):
# Optionally add the leading /sabnzbd/
if not root.startswith('/sabnzbd'):
root = cherrypy.request.script_name + root
# Send the redirect
return cherrypy.HTTPRedirect(root)
@@ -224,7 +224,9 @@ def set_auth(conf):
conf.update({'tools.basic_auth.on': True, 'tools.basic_auth.realm': 'SABnzbd',
'tools.basic_auth.users': get_users, 'tools.basic_auth.encrypt': encrypt_pwd})
conf.update({'/api': {'tools.basic_auth.on': False},
'%s/api' % cfg.url_base(): {'tools.basic_auth.on': False},
'/m/api': {'tools.basic_auth.on': False},
'/sabnzbd/api': {'tools.basic_auth.on': False},
'/sabnzbd/m/api': {'tools.basic_auth.on': False},
})
else:
conf.update({'tools.basic_auth.on': False})
@@ -374,7 +376,7 @@ class MainPage(object):
return template.respond()
else:
# Redirect to the setup wizard
raise cherrypy.HTTPRedirect('%s/wizard/' % cfg.url_base())
raise cherrypy.HTTPRedirect('/sabnzbd/wizard/')
@cherrypy.expose
def addFile(self, **kwargs):
@@ -1154,7 +1156,8 @@ class ConfigPage(object):
conf['have_mt_par2'] = sabnzbd.newsunpack.PAR2_MT
conf['have_ssl_context'] = sabnzbd.HAVE_SSL_CONTEXT
conf['ssl_version'] = ssl.OPENSSL_VERSION
conf['ssl_version'] = ssl_version()
conf['ssl_protocols'] = ', '.join(ssl_protocols_labels())
new = {}
for svr in config.get_servers():
@@ -1304,8 +1307,8 @@ SWITCH_LIST = \
'pre_script', 'pause_on_pwrar', 'sfv_check', 'folder_rename', 'load_balancing',
'quota_size', 'quota_day', 'quota_resume', 'quota_period', 'history_retention',
'pre_check', 'max_art_tries', 'fail_hopeless_jobs', 'enable_all_par',
'enable_recursive', 'no_series_dupes', 'series_propercheck', 'script_can_fail',
'new_nzb_on_failure', 'unwanted_extensions', 'action_on_unwanted_extensions', 'sanitize_safe',
'enable_recursive', 'no_series_dupes', 'script_can_fail', 'new_nzb_on_failure',
'unwanted_extensions', 'action_on_unwanted_extensions', 'sanitize_safe',
'rating_enable', 'rating_api_key', 'rating_filter_enable',
'rating_filter_abort_audio', 'rating_filter_abort_video', 'rating_filter_abort_encrypted',
'rating_filter_abort_encrypted_confirm', 'rating_filter_abort_spam', 'rating_filter_abort_spam_confirm',
@@ -1376,12 +1379,12 @@ SPECIAL_BOOL_LIST = \
'enable_filejoin', 'enable_tsjoin', 'ignore_unrar_dates',
'multipar', 'osx_menu', 'osx_speed', 'win_menu', 'use_pickle', 'allow_incomplete_nzb',
'rss_filenames', 'ipv6_hosting', 'keep_awake', 'empty_postproc', 'html_login', 'wait_for_dfolder',
'max_art_opt', 'warn_empty_nzb', 'enable_bonjour', 'reject_duplicate_files', 'warn_dupl_jobs',
'max_art_opt', 'warn_empty_nzb', 'enable_bonjour','allow_duplicate_files', 'warn_dupl_jobs',
'replace_illegal', 'backup_for_duplicates', 'disable_api_key', 'api_logging',
)
SPECIAL_VALUE_LIST = \
('size_limit', 'folder_max_length', 'fsys_type', 'movie_rename_limit', 'nomedia_marker',
'req_completion_rate', 'wait_ext_drive', 'show_sysload', 'url_base',
'req_completion_rate', 'wait_ext_drive', 'show_sysload',
'direct_unpack_threads', 'ipv6_servers', 'selftest_host', 'rating_host'
)
SPECIAL_LIST_LIST = ('rss_odd_titles', 'quick_check_ext_ignore')
@@ -1519,7 +1522,7 @@ class ConfigGeneral(object):
conf['nzb_key'] = cfg.nzb_key()
conf['local_ranges'] = cfg.local_ranges.get_string()
conf['my_lcldata'] = cfg.admin_dir.get_path()
conf['caller_url'] = cherrypy.request.base + cfg.url_base()
conf['caller_url'] = cherrypy.request.base + '/sabnzbd/'
template = Template(file=os.path.join(sabnzbd.WEB_DIR_CONFIG, 'config_general.tmpl'),
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
@@ -1757,7 +1760,7 @@ class ConfigRss(object):
self.__refresh_force = False # True if forced download of all matches is required
self.__refresh_ignore = False # True if first batch of new feed must be ignored
self.__evaluate = False # True if feed needs to be re-filtered
self.__show_eval_button = False # True if the "Apply filers" button should be shown
self.__show_eval_button = True # True if the "Apply filers" button should be shown
self.__last_msg = '' # Last error message from RSS reader
@cherrypy.expose
@@ -2076,7 +2079,7 @@ class ConfigRss(object):
prio = att.get('prio')
if url:
sabnzbd.add_url(url, pp, script, cat, prio, nzbname, feed_name=feed)
sabnzbd.add_url(url, pp, script, cat, prio, nzbname)
# Need to pass the title instead
sabnzbd.rss.flag_downloaded(feed, url)
raise rssRaiser(self.__root, kwargs)
@@ -2129,7 +2132,6 @@ class ConfigScheduling(object):
actions = []
actions.extend(_SCHED_ACTIONS)
day_names = get_days()
categories = list_cats(False)
snum = 1
conf['schedlines'] = []
conf['taskinfo'] = []
@@ -2162,13 +2164,6 @@ class ConfigScheduling(object):
except KeyError:
value = '"%s" <<< %s' % (value, T('Undefined server!'))
action = Ttemplate("sch-" + action)
if action in ('pause_cat', 'resume_cat'):
action = Ttemplate("sch-" + action)
if value not in categories:
# Category name change
value = '"%s" <<< %s' % (value, T('Incorrect parameter'))
else:
value = '"%s"' % value
if day_numbers == "1234567":
days_of_week = "Daily"
@@ -2196,7 +2191,6 @@ class ConfigScheduling(object):
conf['actions_servers'] = actions_servers
conf['actions'] = actions
conf['actions_lng'] = actions_lng
conf['categories'] = categories
template = Template(file=os.path.join(sabnzbd.WEB_DIR_CONFIG, 'config_scheduling.tmpl'),
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
@@ -2209,7 +2203,6 @@ class ConfigScheduling(object):
return msg
servers = config.get_servers()
categories = list_cats(False)
minute = kwargs.get('minute')
hour = kwargs.get('hour')
days_of_week = ''.join([str(x) for x in kwargs.get('daysofweek', '')])
@@ -2237,12 +2230,7 @@ class ConfigScheduling(object):
else:
arguments = action
action = 'disable_server'
elif action in ('pause_cat', 'resume_cat'):
# Need original category name, not lowercased
arguments = arguments.strip()
else:
# Something else, leave empty
action = None
if action:

View File

@@ -888,16 +888,14 @@ def get_unique_path(dirpath, n=0, create_dir=True):
@synchronized(DIR_LOCK)
def get_unique_filename(path):
""" Check if path is unique.
If not, add number like: "/path/name.NUM.ext".
"""
""" Check if path is unique. If not, add number like: "/path/name.NUM.ext". """
num = 1
new_path, fname = os.path.split(path)
name, ext = os.path.splitext(fname)
while os.path.exists(path):
path, fname = os.path.split(path)
name, ext = os.path.splitext(fname)
fname = "%s.%d%s" % (name, num, ext)
num += 1
path = os.path.join(new_path, fname)
path = os.path.join(path, fname)
return path
@@ -1380,13 +1378,13 @@ def get_all_passwords(nzo):
logging.debug('Read these passwords from file: %s', pws)
passwords.extend(pws)
logging.info('Read %s passwords from file %s', len(pws), pw_file)
# Check size
if len(pws) > 30:
logging.warning(T('Your password file contains more than 30 passwords, testing all these passwords takes a lot of time. Try to only list useful passwords.'))
except:
logging.warning('Failed to read the passwords file %s', pw_file)
# Check size
if len(passwords) > 30:
logging.warning(T('Your password file contains more than 30 passwords, testing all these passwords takes a lot of time. Try to only list useful passwords.'))
if nzo.password:
# If an explicit password was set, add a retry without password, just in case.
passwords.append('')

View File

@@ -69,6 +69,7 @@ SPLITFILE_RE = re.compile(r'\.(\d\d\d$)', re.I)
ZIP_RE = re.compile(r'\.(zip$)', re.I)
SEVENZIP_RE = re.compile(r'\.7z$', re.I)
SEVENMULTI_RE = re.compile(r'\.7z\.\d+$', re.I)
FULLVOLPAR2_RE = re.compile(r'(.*[^.])(\.*vol[0-9]+\+[0-9]+\.par2)', re.I)
TS_RE = re.compile(r'\.(\d+)\.(ts$)', re.I)
PAR2_COMMAND = None
@@ -1042,10 +1043,14 @@ def seven_extract_core(sevenset, extensions, extraction_path, one_folder, delete
##############################################################################
def par2_repair(parfile_nzf, nzo, workdir, setname, single):
""" Try to repair a set, return readd or correctness """
# set the current nzo status to "Repairing". Used in History
assert isinstance(nzo, sabnzbd.nzbstuff.NzbObject)
# Check if file exists, otherwise see if another is done
parfile_path = os.path.join(workdir, parfile_nzf.filename)
if not os.path.exists(parfile_path) and nzo.extrapars[setname]:
for new_par in nzo.extrapars[setname]:
if not os.path.exists(parfile_path) and parfile_nzf.extrapars:
for new_par in parfile_nzf.extrapars:
test_parfile = os.path.join(workdir, new_par.filename)
if os.path.exists(test_parfile):
parfile_nzf = new_par
@@ -1054,29 +1059,31 @@ def par2_repair(parfile_nzf, nzo, workdir, setname, single):
# No file was found, we assume this set already finished
return False, True
# Shorten just the workdir on Windows
parfile = os.path.join(workdir, parfile_nzf.filename)
old_dir_content = os.listdir(workdir)
used_joinables = ()
joinables = ()
used_for_repair = ()
setpars = pars_of_set(workdir, setname)
result = readd = False
# Need to copy now, gets pop-ed during repair
setpars = nzo.extrapars[setname][:]
# Start QuickCheck
nzo.status = Status.QUICK_CHECK
nzo.set_action_line(T('Repair'), T('Quick Checking'))
qc_result = QuickCheck(setname, nzo)
if qc_result:
logging.info("Quick-check for %s is OK, skipping repair", setname)
nzo.set_unpack_info('Repair', T('[%s] Quick Check OK') % unicoder(setname))
pars = setpars
result = True
if not result and cfg.enable_all_par():
# Download all par2 files that haven't been downloaded yet
readd = False
for extrapar in nzo.extrapars[setname][:]:
for extrapar in parfile_nzf.extrapars[:]:
parfile_nzf.extrapars.remove(extrapar)
parfile_nzf.nzo.remove_extrapar(extrapar)
if extrapar not in nzo.finished_files and extrapar not in nzo.files:
nzo.add_parfile(extrapar)
readd = True
@@ -1095,9 +1102,11 @@ def par2_repair(parfile_nzf, nzo, workdir, setname, single):
# Multipar or not?
if sabnzbd.WIN32 and cfg.multipar():
finished, readd, datafiles, used_joinables, used_for_repair = MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=single)
finished, readd, pars, datafiles, used_joinables, used_for_repair = MultiPar_Verify(parfile, parfile_nzf, nzo,
setname, joinables, single=single)
else:
finished, readd, datafiles, used_joinables, used_for_repair = PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=single)
finished, readd, pars, datafiles, used_joinables, used_for_repair = PAR_Verify(parfile, parfile_nzf, nzo,
setname, joinables, single=single)
if finished:
result = True
@@ -1139,8 +1148,9 @@ def par2_repair(parfile_nzf, nzo, workdir, setname, single):
deletables.extend(used_joinables)
deletables.extend([os.path.join(workdir, f) for f in used_for_repair])
# Delete pars of the set
deletables.extend([os.path.join(workdir, nzf.filename) for nzf in setpars])
# Delete pars of the set and maybe extra ones that par2 found
deletables.extend([os.path.join(workdir, f) for f in setpars])
deletables.extend([os.path.join(workdir, f) for f in pars])
for filepath in deletables:
if filepath in joinables:
@@ -1154,7 +1164,7 @@ def par2_repair(parfile_nzf, nzo, workdir, setname, single):
except:
msg = sys.exc_info()[1]
nzo.fail_msg = T('Repairing failed, %s') % msg
logging.error(T('Error "%s" while running par2_repair on set %s'), msg, setname, exc_info=True)
logging.error(T('Error "%s" while running par2_repair on set %s'), msg, setname)
return readd, result
@@ -1169,6 +1179,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False):
""" Run par2 on par-set """
used_joinables = []
used_for_repair = []
extra_par2_name = None
# set the current nzo status to "Verifying...". Used in History
nzo.status = Status.VERIFYING
start = time.time()
@@ -1210,10 +1221,10 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False):
# par2multicore wants to see \\.\ paths on Windows
# See: https://github.com/sabnzbd/sabnzbd/pull/771
if sabnzbd.WIN32:
command = [clip_path(x) if x.startswith('\\\\?\\') else x for x in command]
command = [x.replace('\\\\?\\', '\\\\.\\', 1) if x.startswith('\\\\?\\') else x for x in command]
# Run the external command
logging.info('Starting par2: %s', command)
logging.debug('Starting par2: %s', command)
lines = []
try:
p = Popen(command, shell=need_shell, stdin=subprocess.PIPE,
@@ -1226,6 +1237,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False):
p.stdin.close()
# Set up our variables
pars = []
datafiles = []
renames = {}
reconstructed = []
@@ -1271,6 +1283,16 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False):
if 'Repairing:' not in line:
lines.append(line)
if extra_par2_name and line.startswith('Loading:') and line.endswith('%'):
continue
if extra_par2_name and line.startswith('Loaded '):
m = _RE_LOADED_PAR2.search(line)
if m and int(m.group(1)) > 0:
used_for_repair.append(extra_par2_name)
extra_par2_name = None
continue
extra_par2_name = None
if line.startswith(('Invalid option specified', 'Invalid thread option', 'Cannot specify recovery file count')):
msg = T('[%s] PAR2 received incorrect options, check your Config->Switches settings') % unicoder(setname)
nzo.set_unpack_info('Repair', msg)
@@ -1295,14 +1317,21 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False):
verifytotal = 0
verifynum = 0
elif line.startswith('Loading "'):
# Found an extra par2 file. Only the next line will tell whether it's usable
m = _RE_LOADING_PAR2.search(line)
if m and m.group(1).lower().endswith('.par2'):
extra_par2_name = TRANS(m.group(1))
elif line.startswith('Main packet not found') or 'The recovery file does not exist' in line:
# Initialparfile probably didn't decode properly,
logging.info(T('Main packet not found...'))
logging.info("Extra pars = %s", nzo.extrapars[setname])
extrapars = parfile_nzf.extrapars
logging.info("Extra pars = %s", extrapars)
# Look for the smallest par2file
block_table = {}
for nzf in nzo.extrapars[setname]:
for nzf in extrapars:
if not nzf.completed:
block_table[int_conv(nzf.blocks)] = nzf
@@ -1311,30 +1340,81 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False):
logging.info("Found new par2file %s", nzf.filename)
# Move from extrapar list to files to be downloaded
# and remove it from the extrapars list
nzo.add_parfile(nzf)
# Now set new par2 file as primary par2
nzo.partable[setname] = nzf
nzf.extrapars = extrapars
parfile_nzf = []
# mark for readd
readd = True
else:
msg = T('Invalid par2 files or invalid PAR2 parameters, cannot verify or repair')
msg = T('Invalid par2 files, cannot verify or repair')
nzo.fail_msg = msg
msg = u'[%s] %s' % (unicoder(setname), msg)
nzo.set_unpack_info('Repair', msg)
nzo.status = Status.FAILED
elif line.startswith('You need'):
# We need more blocks, but are they available?
chunks = line.split()
needed_blocks = int(chunks[2])
avail_blocks = 0
logging.info('Need to fetch %s more blocks, checking blocks', needed_blocks)
# Check if we have enough blocks
added_blocks = nzo.get_extra_blocks(setname, needed_blocks)
if added_blocks:
msg = T('Fetching %s blocks...') % str(added_blocks)
nzo.set_action_line(T('Fetching'), msg)
extrapars = parfile_nzf.extrapars
block_table = {}
for nzf in extrapars:
# Don't count extrapars that are completed already
if nzf.completed:
continue
blocks = int_conv(nzf.blocks)
avail_blocks += blocks
if blocks not in block_table:
block_table[blocks] = []
block_table[blocks].append(nzf)
logging.info('%s blocks available', avail_blocks)
force = False
if (avail_blocks < needed_blocks) and (avail_blocks > 0):
# Tell SAB that we always have enough blocks, so that
# it will try to load all pars anyway
msg = T('Repair failed, not enough repair blocks (%s short)') % str(int(needed_blocks - avail_blocks))
nzo.fail_msg = msg
msg = u'[%s] %s' % (unicoder(setname), msg)
nzo.set_unpack_info('Repair', msg)
nzo.status = Status.FETCHING
needed_blocks = avail_blocks
force = True
if avail_blocks >= needed_blocks:
added_blocks = 0
readd = True
while added_blocks < needed_blocks:
block_size = min(block_table.keys())
extrapar_list = block_table[block_size]
if extrapar_list:
new_nzf = extrapar_list.pop()
nzo.add_parfile(new_nzf)
if new_nzf in extrapars:
extrapars.remove(new_nzf)
added_blocks += block_size
else:
block_table.pop(block_size)
logging.info('Added %s blocks to %s', added_blocks, nzo.final_name)
if not force:
msg = T('Fetching %s blocks...') % str(added_blocks)
nzo.status = Status.FETCHING
nzo.set_action_line(T('Fetching'), msg)
else:
# Failed
msg = T('Repair failed, not enough repair blocks (%s short)') % str(needed_blocks)
nzo.fail_msg = msg
msg = u'[%s] %s' % (unicoder(setname), msg)
@@ -1448,15 +1528,20 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False):
elif line.startswith('Scanning:'):
pass
else:
# Loading parity files
m = LOADING_RE.match(line)
if m:
pars.append(TRANS(m.group(1)))
continue
# Target files
m = TARGET_RE.match(line)
if m:
nzo.status = Status.VERIFYING
verifynum += 1
if verifytotal == 0 or verifynum < verifytotal:
verifynum += 1
nzo.set_action_line(T('Verifying'), '%02d/%02d' % (verifynum, verifytotal))
else:
nzo.set_action_line(T('Checking extra files'), '%02d' % verifynum)
nzo.status = Status.VERIFYING
# Remove redundant extra files that are just duplicates of original ones
if 'duplicate data blocks' in line:
@@ -1485,7 +1570,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False):
# Use 'used_joinables' as a vehicle to get rid of the files
used_joinables.extend(reconstructed)
return finished, readd, datafiles, used_joinables, used_for_repair
return finished, readd, pars, datafiles, used_joinables, used_for_repair
_RE_FILENAME = re.compile(r'"([^"]+)"')
@@ -1518,6 +1603,7 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
command.append(os.path.join(parfolder, wildcard))
stup, need_shell, command, creationflags = build_command(command)
# CHANGE TO DEBUG LATER
logging.info('Starting MultiPar: %s', command)
lines = []
@@ -1531,6 +1617,7 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
p.stdin.close()
# Set up our variables
pars = []
datafiles = []
renames = {}
reconstructed = []
@@ -1542,6 +1629,7 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
verifynum = 0
verifytotal = 0
in_parlist = False
in_check = False
in_verify = False
in_repair = False
@@ -1590,12 +1678,13 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
elif line.startswith('valid file is not found'):
# Initialparfile probably didn't decode properly,
extrapars = parfile_nzf.extrapars
logging.info(T('Main packet not found...'))
logging.info("Extra pars = %s", nzo.extrapars[setname])
logging.info("Extra pars = %s", extrapars)
# Look for the smallest par2file
block_table = {}
for nzf in nzo.extrapars[setname]:
for nzf in extrapars:
if not nzf.completed:
block_table[int_conv(nzf.blocks)] = nzf
@@ -1604,11 +1693,15 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
logging.info("Found new par2file %s", nzf.filename)
# Move from extrapar list to files to be downloaded
# and remove it from the extrapars list
nzo.add_parfile(nzf)
# Now set new par2 file as primary par2
nzo.partable[setname] = nzf
nzf.extrapars = extrapars
parfile_nzf = []
# mark for readd
readd = True
else:
msg = T('Invalid par2 files or invalid PAR2 parameters, cannot verify or repair')
msg = T('Invalid par2 files, cannot verify or repair')
nzo.fail_msg = msg
msg = u'[%s] %s' % (unicoder(setname), msg)
nzo.set_unpack_info('Repair', msg)
@@ -1622,6 +1715,18 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
nzo.status = Status.FAILED
# ----------------- Start check/verify stage
# List of Par2 files we will use today
if line.startswith('PAR File list'):
in_parlist = True
if line.startswith('PAR File total size'):
# Ende of the Par2 listing
in_parlist = False
elif in_parlist:
m = _RE_FILENAME.search(line)
if m:
used_for_repair.append(TRANS(m.group(1)))
pars.append(TRANS(m.group(1)))
elif line.startswith('Recovery Set ID'):
# Remove files were MultiPar stores verification result when repaired succesfull
recovery_id = line.split()[-1]
@@ -1698,9 +1803,9 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
# It prints the filename couple of times, so we save it to check
# 'datafiles' will not contain all data-files in par-set, only the
# ones that got scanned, but it's ouput is never used!
nzo.status = Status.VERIFYING
if line.split()[1] in ('Damaged', 'Found'):
verifynum += 1
nzo.status = Status.VERIFYING
datafiles.append(TRANS(m.group(1)))
# Set old_name in case it was misnamed and found (not when we are joining)
@@ -1727,13 +1832,6 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
# Need to reset it to avoid collision
old_name = None
else:
# It's scanning extra files that don't belong to the set
# For damaged files it reports the filename twice, so only then start
verifynum += 1
if verifynum / 2 > verifytotal:
nzo.set_action_line(T('Checking extra files'), '%02d' % verifynum)
if joinables:
# Find out if a joinable file has been used for joining
uline = unicoder(line)
@@ -1744,19 +1842,62 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
break
elif line.startswith('Need'):
# We need more blocks, but are they available?
# We need more blocks, but are they there?
chunks = line.split()
needed_blocks = int(chunks[1])
avail_blocks = 0
extrapars = parfile_nzf.extrapars
block_table = {}
logging.info('Need to fetch %s more blocks, checking blocks', needed_blocks)
# Check if we have enough blocks
added_blocks = nzo.get_extra_blocks(setname, needed_blocks)
if added_blocks:
msg = T('Fetching %s blocks...') % str(added_blocks)
nzo.set_action_line(T('Fetching'), msg)
for nzf in extrapars:
# Don't count extrapars that are completed already
if nzf.completed:
continue
blocks = int_conv(nzf.blocks)
avail_blocks += blocks
if blocks not in block_table:
block_table[blocks] = []
block_table[blocks].append(nzf)
logging.info('%s blocks available', avail_blocks)
force = False
if (avail_blocks < needed_blocks) and (avail_blocks > 0):
# Tell SAB that we always have enough blocks, so that
# it will try to load all pars anyway
msg = T('Repair failed, not enough repair blocks (%s short)') % str(int(needed_blocks - avail_blocks))
nzo.fail_msg = msg
msg = u'[%s] %s' % (unicoder(setname), msg)
nzo.set_unpack_info('Repair', msg)
nzo.status = Status.FETCHING
needed_blocks = avail_blocks
force = True
if avail_blocks >= needed_blocks:
added_blocks = 0
readd = True
while added_blocks < needed_blocks:
block_size = min(block_table.keys())
extrapar_list = block_table[block_size]
if extrapar_list:
new_nzf = extrapar_list.pop()
nzo.add_parfile(new_nzf)
if new_nzf in extrapars:
extrapars.remove(new_nzf)
added_blocks += block_size
else:
block_table.pop(block_size)
logging.info('Added %s blocks to %s',
added_blocks, nzo.final_name)
if not force:
msg = T('Fetching %s blocks...') % str(added_blocks)
nzo.status = Status.FETCHING
nzo.set_action_line(T('Fetching'), msg)
else:
# Failed
msg = T('Repair failed, not enough repair blocks (%s short)') % str(needed_blocks)
nzo.fail_msg = msg
msg = u'[%s] %s' % (unicoder(setname), msg)
@@ -1764,10 +1905,8 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
nzo.status = Status.FAILED
# Result of verification
elif line.startswith('All Files Complete') or line.endswith('PAR File(s) Incomplete'):
elif line.startswith('All Files Complete'):
# Completed without damage!
# 'PAR File(s) Incomplete' is reported for success
# but when there are very similar filenames in the folder
msg = T('[%s] Verified in %s, all files correct') % (unicoder(setname), format_time_string(time.time() - start))
nzo.set_unpack_info('Repair', msg)
logging.info('Verified in %s, all files correct',
@@ -1839,7 +1978,7 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
workdir = os.path.split(parfile)[0]
used_joinables.extend([os.path.join(workdir, name) for name in reconstructed])
return finished, readd, datafiles, used_joinables, used_for_repair
return finished, readd, pars, datafiles, used_joinables, used_for_repair
def create_env(nzo=None, extra_env_fields=None):
""" Modify the environment for pp-scripts with extra information
@@ -2118,6 +2257,16 @@ def QuickCheck(set, nzo):
return result
def pars_of_set(wdir, setname):
""" Return list of par2 files (pathless) matching the set """
list = []
for file in os.listdir(wdir):
m = FULLVOLPAR2_RE.search(file)
if m and m.group(1) == setname and m.group(2):
list.append(file)
return list
def unrar_check(rar):
""" Return version number of unrar, where "5.01" returns 501
Also return whether an original version is found

View File

@@ -441,12 +441,6 @@ class NzbQueue(object):
removed.append(nzo_id)
# Save with invalid nzo_id, to that only queue file is saved
self.save('x')
# Any files left? Otherwise let's disconnect
if self.actives(grabs=False) == 0 and cfg.autodisconnect():
# This was the last job, close server connections
sabnzbd.downloader.Downloader.do.disconnect()
return removed
def remove_all(self, search=None):
@@ -721,13 +715,15 @@ class NzbQueue(object):
def get_article(self, server, servers):
for nzo in self.__nzo_list:
# Not when queue paused and not a forced item
if nzo.status not in (Status.PAUSED, Status.GRABBING) or nzo.priority == TOP_PRIORITY:
if (nzo.status not in (Status.PAUSED, Status.GRABBING) and not sabnzbd.downloader.Downloader.do.paused) or nzo.priority == TOP_PRIORITY:
# Check if past propagation delay, or forced
if not cfg.propagation_delay() or nzo.priority == TOP_PRIORITY or (nzo.avg_stamp + float(cfg.propagation_delay() * 60)) < time.time():
if not nzo.server_in_try_list(server):
article = nzo.get_article(server, servers)
if article:
return article
# Don't try to get an article if server is in try_list of nzo and category allowed by server
if nzo.server_allowed(server):
if not nzo.server_in_try_list(server):
article = nzo.get_article(server, servers)
if article:
return article
# Stop after first job that wasn't paused/propagating/etc
if self.__top_only:
return
@@ -762,9 +758,7 @@ class NzbQueue(object):
# Only start decoding if we have a filename and type
if filename and _type:
Assembler.do.process((nzo, nzf))
elif filename.lower().endswith('.par2'):
# Broken par2 file, try to get another one
nzo.promote_par2(nzf)
else:
if file_has_articles(nzf):
logging.warning(T('%s -> Unknown encoding'), filename)
@@ -817,8 +811,8 @@ class NzbQueue(object):
n = 0
for nzo in self.__nzo_list:
if nzo.status not in (Status.PAUSED, Status.CHECKING) or nzo.priority == TOP_PRIORITY:
b_left = nzo.remaining
if nzo.status not in (Status.PAUSED, Status.CHECKING):
b_left = nzo.remaining()
bytes_total += nzo.bytes
bytes_left += b_left
q_size += 1
@@ -840,7 +834,7 @@ class NzbQueue(object):
bytes_left = 0
for nzo in self.__nzo_list:
if nzo.status != 'Paused':
bytes_left += nzo.remaining
bytes_left += nzo.remaining()
return bytes_left
def is_empty(self):
@@ -864,41 +858,18 @@ class NzbQueue(object):
if not nzo.futuretype and not nzo.files and nzo.status not in (Status.PAUSED, Status.GRABBING):
empty.append(nzo)
# Stall prevention by checking if all servers are in the trylist
# This is a CPU-cheaper alternative to prevent stalling
if len(nzo.try_list) == sabnzbd.downloader.Downloader.do.server_nr:
# Maybe the NZF's need a reset too?
for nzf in nzo.files:
if len(nzf.try_list) == sabnzbd.downloader.Downloader.do.server_nr:
# We do not want to reset all article trylists, they are good
nzf.reset_try_list()
# Reset main trylist, minimal performance impact
nzo.reset_try_list()
for nzo in empty:
self.end_job(nzo)
def pause_on_prio(self, priority):
for nzo in self.__nzo_list:
if nzo.priority == priority:
if not nzo.futuretype and nzo.priority == priority:
nzo.pause()
@notify_downloader
def resume_on_prio(self, priority):
for nzo in self.__nzo_list:
if nzo.priority == priority:
# Don't use nzo.resume() to avoid resetting job warning flags
nzo.status = Status.QUEUED
def pause_on_cat(self, cat):
for nzo in self.__nzo_list:
if nzo.cat == cat:
nzo.pause()
@notify_downloader
def resume_on_cat(self, cat):
for nzo in self.__nzo_list:
if nzo.cat == cat:
if not nzo.futuretype and nzo.priority == priority:
# Don't use nzo.resume() to avoid resetting job warning flags
nzo.status = Status.QUEUED

View File

@@ -30,7 +30,6 @@ import xml.sax
import xml.sax.handler
import xml.sax.xmlreader
import hashlib
import difflib
try:
from cStringIO import StringIO
@@ -46,8 +45,7 @@ from sabnzbd.constants import GIGI, ATTRIB_FILE, JOB_ADMIN, \
from sabnzbd.misc import to_units, cat_to_opts, cat_convert, sanitize_foldername, \
get_unique_path, get_admin_path, remove_all, sanitize_filename, globber_full, \
int_conv, set_permissions, format_time_string, long_path, trim_win_path, \
fix_unix_encoding, calc_age, is_obfuscated_filename, get_ext, get_filename, \
get_unique_filename, renamer
fix_unix_encoding, calc_age, is_obfuscated_filename
from sabnzbd.decorators import synchronized
import sabnzbd.config as config
import sabnzbd.cfg as cfg
@@ -59,10 +57,11 @@ __all__ = ['Article', 'NzbFile', 'NzbObject']
# Name patterns
SUBJECT_FN_MATCHER = re.compile(r'"([^"]*)"')
RE_NORMAL_NAME = re.compile(r'\.\w{1,5}$') # Test reasonably sized extension at the end
PROBABLY_PAR2_RE = re.compile(r'(.*)\.vol(\d*)[\+\-](\d*)\.par2', re.I)
REJECT_PAR2_RE = re.compile(r'\.par2\.\d+', re.I) # Reject duplicate par2 files
RE_NORMAL_NAME = re.compile(r'\.\w{2,5}$') # Test reasonably sized extension at the end
RE_QUICK_PAR2_CHECK = re.compile(r'\.par2\W*', re.I)
RE_RAR = re.compile(r'(\.rar|\.r\d\d|\.s\d\d|\.t\d\d|\.u\d\d|\.v\d\d)$', re.I)
RE_PROPER = re.compile(r'(^|[\. _-])(PROPER|REAL|REPACK)([\. _-]|$)')
##############################################################################
@@ -75,37 +74,27 @@ class TryList(object):
""" TryList keeps track of which servers have been tried for a specific article
"""
# Pre-define attributes to save memory
__slots__ = ('try_list', 'fetcher_priority')
__slots__ = ('__try_list', 'fetcher_priority')
def __init__(self):
self.try_list = []
self.__try_list = []
self.fetcher_priority = 0
def server_in_try_list(self, server):
""" Return whether specified server has been tried """
with TRYLIST_LOCK:
return server in self.try_list
return server in self.__try_list
def add_to_try_list(self, server):
""" Register server as having been tried already """
with TRYLIST_LOCK:
if server not in self.try_list:
self.try_list.append(server)
if server not in self.__try_list:
self.__try_list.append(server)
def reset_try_list(self):
""" Clean the list """
with TRYLIST_LOCK:
self.try_list = []
def __getstate__(self):
""" Save the servers """
return [server.id for server in self.try_list]
def __setstate__(self, servers_ids):
self.try_list = []
for server_id in servers_ids:
if server_id in sabnzbd.downloader.Downloader.do.server_dict:
self.add_to_try_list(sabnzbd.downloader.Downloader.do.server_dict[server_id])
self.__try_list = []
##############################################################################
@@ -166,7 +155,7 @@ class Article(TryList):
# if (server_check.priority() < found_priority and server_check.priority() < server.priority and not self.server_in_try_list(server_check)):
if server_check.active and (server_check.priority < found_priority):
if server_check.priority < server.priority:
if (not self.server_in_try_list(server_check)):
if (not self.server_in_try_list(server_check)) and self.server_allowed(server_check):
if log:
logging.debug('Article %s | Server: %s | setting found priority to %s', self.article, server.host, server_check.priority)
found_priority = server_check.priority
@@ -193,12 +182,15 @@ class Article(TryList):
self.art_id = sabnzbd.get_new_id("article", self.nzf.nzo.workpath)
return self.art_id
def server_allowed(self, server):
""" Return true if this server is allowed to download this article. """
return self.nzf.nzo.server_allowed(server)
def __getstate__(self):
""" Save to pickle file, selecting attributes """
dict_ = {}
for item in ArticleSaver:
dict_[item] = getattr(self, item)
dict_['try_list'] = TryList.__getstate__(self)
return dict_
def __setstate__(self, dict_):
@@ -209,9 +201,9 @@ class Article(TryList):
except KeyError:
# Handle new attributes
setattr(self, item, None)
TryList.__setstate__(self, dict_.get('try_list', []))
self.fetcher_priority = 0
TryList.__init__(self)
self.fetcher = None
self.fetcher_priority = 0
self.tries = 0
def __repr__(self):
@@ -224,7 +216,7 @@ class Article(TryList):
##############################################################################
NzbFileSaver = (
'date', 'subject', 'filename', 'filename_checked', 'type', 'is_par2', 'vol',
'blocks', 'setname', 'articles', 'decodetable', 'bytes', 'bytes_left',
'blocks', 'setname','extrapars', 'articles', 'decodetable', 'bytes', 'bytes_left',
'article_count', 'nzo', 'nzf_id', 'deleted', 'valid', 'import_finished',
'md5sum', 'md5of16k'
)
@@ -249,6 +241,7 @@ class NzbFile(TryList):
self.vol = None
self.blocks = None
self.setname = None
self.extrapars = None
self.articles = []
self.decodetable = {}
@@ -300,7 +293,10 @@ class NzbFile(TryList):
self.articles.remove(article)
if found:
self.bytes_left -= article.bytes
# To keep counter correct for pre-check
if self.nzo.precheck:
self.nzo.bytes_downloaded += article.bytes
self.nzo.bytes_tried += article.bytes
return (not self.articles)
def set_par2(self, setname, vol, blocks):
@@ -311,12 +307,27 @@ class NzbFile(TryList):
self.blocks = int(blocks)
def get_article(self, server, servers):
""" Get next article to be downloaded """
""" Get next article to be downloaded from this server
Returns None when there are still articles to try
Returns False when all articles are tried
"""
# Make sure all articles have tried this server before
# adding to the NZF-TryList, otherwise there will be stalls!
tried_all_articles = True
for article in self.articles:
article = article.get_article(server, servers)
if article:
return article
self.add_to_try_list(server)
article_return = article.get_article(server, servers)
if article_return:
return article_return
elif tried_all_articles and not article.server_in_try_list(server):
tried_all_articles = False
# We are sure they are all tried
if tried_all_articles:
self.add_to_try_list(server)
return False
# Still articles left to try
return None
def reset_all_try_lists(self):
""" Clear all lists of visited servers """
@@ -346,7 +357,6 @@ class NzbFile(TryList):
dict_ = {}
for item in NzbFileSaver:
dict_[item] = getattr(self, item)
dict_['try_list'] = TryList.__getstate__(self)
return dict_
def __setstate__(self, dict_):
@@ -357,7 +367,7 @@ class NzbFile(TryList):
except KeyError:
# Handle new attributes
setattr(self, item, None)
TryList.__setstate__(self, dict_.get('try_list', []))
TryList.__init__(self)
def __repr__(self):
return "<NzbFile: filename=%s, type=%s>" % (self.filename, self.type)
@@ -492,10 +502,14 @@ class NzbParser(xml.sax.handler.ContentHandler):
nzf = NzbFile(tm, self.filename, self.article_db, self.file_bytes, self.nzo)
# Check if file was added with same name
if cfg.reject_duplicate_files():
if not cfg.allow_duplicate_files():
nzo_matches = filter(lambda x: (x.filename == nzf.filename), self.nzo.files)
if nzo_matches:
logging.info('File %s occured twice in NZB, discarding smaller file', nzf.filename)
# Keep some logging how many were duplicates
if 'duplicate_files' not in self.nzo.nzo_info:
self.nzo.nzo_info['duplicate_files'] = 0
self.nzo.nzo_info['duplicate_files'] += 1
# Which is smaller? Current or old one
if nzo_matches[0].bytes >= nzf.bytes:
@@ -556,9 +570,9 @@ class NzbParser(xml.sax.handler.ContentHandler):
##############################################################################
NzbObjectSaver = (
'filename', 'work_name', 'final_name', 'created', 'bytes', 'bytes_downloaded', 'bytes_tried',
'bytes_missing', 'repair', 'unpack', 'delete', 'script', 'cat', 'url', 'groups', 'avg_date',
'md5of16k', 'partable', 'extrapars', 'md5packs', 'files', 'files_table', 'finished_files',
'status', 'avg_bps_freq', 'avg_bps_total', 'priority', 'saved_articles', 'nzo_id',
'repair', 'unpack', 'delete', 'script', 'cat', 'url', 'groups', 'avg_date', 'md5of16k',
'partable', 'extrapars', 'md5packs', 'files', 'files_table', 'finished_files', 'status',
'avg_bps_freq', 'avg_bps_total', 'priority', 'saved_articles', 'nzo_id',
'futuretype', 'deleted', 'parsed', 'action_line', 'unpack_info', 'fail_msg', 'nzo_info',
'custom_name', 'password', 'next_save', 'save_timeout', 'encrypted', 'bad_articles',
'duplicate', 'oversized', 'precheck', 'incomplete', 'reuse', 'meta',
@@ -571,7 +585,6 @@ NZO_LOCK = threading.RLock()
class NzbObject(TryList):
@synchronized(NZO_LOCK)
def __init__(self, filename, pp, script, nzb=None,
futuretype=False, cat=None, url=None,
priority=NORMAL_PRIORITY, nzbname=None, status="Queued", nzo_info=None,
@@ -616,7 +629,6 @@ class NzbObject(TryList):
self.bytes = 0 # Original bytesize
self.bytes_downloaded = 0 # Downloaded byte
self.bytes_tried = 0 # Which bytes did we try
self.bytes_missing = 0 # Bytes missing
self.bad_articles = 0 # How many bad (non-recoverable) articles
self.repair = r # True if we want to repair this set
self.unpack = u # True if we want to unpack this set
@@ -886,6 +898,10 @@ class NzbObject(TryList):
else:
self.files.sort(cmp=nzf_cmp_name)
# Warn if there were many duplicate files
if 'duplicate_files' in self.nzo_info and self.nzo_info['duplicate_files'] >= 10:
logging.warning(T('%d files with duplicate filenames were discared for "%s". Enable "allow_duplicate_files" to allow duplicate filenames.'), self.nzo_info['duplicate_files'], self.final_name)
# In the hunt for Unwanted Extensions:
# The file with the unwanted extension often is in the first or the last rar file
# So put the last rar immediately after the first rar file so that it gets detected early
@@ -944,6 +960,7 @@ class NzbObject(TryList):
self.servercount[serverid] += bytes
else:
self.servercount[serverid] = bytes
self.bytes_downloaded += bytes
@synchronized(NZO_LOCK)
def remove_nzf(self, nzf):
@@ -963,138 +980,73 @@ class NzbObject(TryList):
@synchronized(NZO_LOCK)
def postpone_pars(self, nzf, parset):
""" Move all vol-par files matching 'parset' to the extrapars table """
# Create new extrapars if it didn't already exist
# For example if created when the first par2 file was missing
if parset not in self.extrapars:
self.extrapars[parset] = []
# Set this one as the main one
self.partable[parset] = nzf
self.extrapars[parset] = []
nzf.extrapars = self.extrapars[parset]
lparset = parset.lower()
for xnzf in self.files[:]:
name = xnzf.filename or platform_encode(xnzf.subject)
# Move only when not current NZF and filename was extractable from subject
if name:
setname, vol, block = sabnzbd.par2file.analyse_par2(name)
# Don't postpone header-only-files, to extract all possible md5of16k
if setname and block and matcher(lparset, setname.lower()):
if name and nzf is not xnzf:
head, vol, block = analyse_par2(name)
if head and matcher(lparset, head.lower()):
xnzf.set_par2(parset, vol, block)
# Don't postpone if all par2 are desired and should be kept or not repairing
if self.repair and not(cfg.enable_all_par() and not cfg.enable_par_cleanup()):
# Don't postpone if all par2 are desired and should be kept
# Also don't postpone header-only-files, to extract all possible md5of16k
if not(cfg.enable_all_par() and not cfg.enable_par_cleanup()) and block:
self.extrapars[parset].append(xnzf)
self.files.remove(xnzf)
# Already count these bytes as done
self.bytes_tried += xnzf.bytes_left
# Sort the sets
for setname in self.extrapars:
self.extrapars[parset].sort(key=lambda x: x.blocks)
@synchronized(NZO_LOCK)
def handle_par2(self, nzf, filepath):
def handle_par2(self, nzf, file_done):
""" Check if file is a par2 and build up par2 collection """
# Need to remove it from the other set it might be in
self.remove_extrapar(nzf)
fn = nzf.filename
if fn:
# We have a real filename now
fn = fn.strip()
if not nzf.is_par2:
head, vol, block = analyse_par2(fn)
# Is a par2file and repair mode activated
if head and self.repair:
# Skip if mini-par2 is not complete and there are more par2 files
if not block and nzf.bytes_left and self.extrapars.get(head):
return
nzf.set_par2(head, vol, block)
# Already got a parfile for this set?
if head in self.partable:
nzf.extrapars = self.extrapars[head]
# Set the smallest par2file as initialparfile
# But only do this if our last initialparfile
# isn't already done (e.g two small parfiles)
if nzf.blocks < self.partable[head].blocks \
and self.partable[head] in self.files:
self.partable[head].reset_try_list()
self.files.remove(self.partable[head])
self.extrapars[head].append(self.partable[head])
self.partable[head] = nzf
# Reparse
setname, vol, block = sabnzbd.par2file.analyse_par2(nzf.filename, filepath)
nzf.set_par2(setname, vol, block)
# This file either has more blocks,
# or initialparfile is already decoded
else:
if file_done:
if nzf in self.files:
self.files.remove(nzf)
if nzf not in self.extrapars[head]:
self.extrapars[head].append(nzf)
else:
nzf.reset_try_list()
# Parse the file contents for hashes
pack = sabnzbd.par2file.parse_par2_file(nzf, filepath)
# If we couldn't parse it, we ignore it
if pack:
if pack not in self.md5packs.values():
logging.debug('Got md5pack for set %s', nzf.setname)
self.md5packs[setname] = pack
# See if we need to postpone some pars
self.postpone_pars(nzf, setname)
else:
# Need to add this to the set, first need setname
for setname in self.md5packs:
if self.md5packs[setname] == pack:
break
# Change the properties
nzf.set_par2(setname, vol, block)
logging.debug('Got additional md5pack for set %s', nzf.setname)
self.extrapars[setname].append(nzf)
elif self.repair:
# For some reason this par2 file is broken but we still want repair
self.promote_par2(nzf)
# Is it an obfuscated file?
if get_ext(nzf.filename) != '.par2':
# Do cheap renaming so it gets better picked up by par2
# Only basename has to be the same
new_fname = get_unique_filename(os.path.join(self.downpath, '%s.par2' % setname))
renamer(filepath, new_fname)
self.renamed_file(get_filename(new_fname), nzf.filename)
nzf.filename = get_filename(new_fname)
@synchronized(NZO_LOCK)
def promote_par2(self, nzf):
""" In case of a broken par2 or missing par2, move another
of the same set to the top (if we can find it)
"""
setname, vol, block = sabnzbd.par2file.analyse_par2(nzf.filename)
# Now we need to identify if we have more in this set
if setname and self.repair:
# Maybe it was the first one
if setname not in self.extrapars:
self.postpone_pars(nzf, setname)
# Get the next one
for new_nzf in self.extrapars[setname]:
if not new_nzf.completed:
self.add_parfile(new_nzf)
# Add it to the top
self.files.remove(new_nzf)
self.files.insert(0, new_nzf)
break
def get_extra_blocks(self, setname, needed_blocks):
""" We want par2-files of all sets that are similar to this one
So that we also can handle multi-sets with duplicate filenames
Block-table has as keys the nr-blocks
Returns number of added blocks in case they are available
"""
logging.info('Need %s more blocks, checking blocks', needed_blocks)
avail_blocks = 0
block_table = {}
for setname_search in self.extrapars:
# Do it for our set, or highlight matching one
# We might catch to many par2's, but that's okay
if setname_search == setname or difflib.SequenceMatcher(None, setname, setname_search).ratio() > 0.85:
for nzf in self.extrapars[setname_search]:
# Don't count extrapars that are completed already
if nzf.completed:
continue
blocks = int_conv(nzf.blocks)
if blocks not in block_table:
block_table[blocks] = []
# We assume same block-vol-naming for each set
avail_blocks += blocks
block_table[blocks].append(nzf)
logging.info('%s blocks available', avail_blocks)
# Enough?
if avail_blocks >= needed_blocks:
added_blocks = 0
while added_blocks < needed_blocks:
block_size = min(block_table.keys())
for new_nzf in block_table[block_size]:
self.add_parfile(new_nzf)
added_blocks += block_size
block_table.pop(block_size)
logging.info('Added %s blocks to %s', added_blocks, self.final_name)
return added_blocks
# No par2file in this set yet, set this as
# initialparfile
else:
self.postpone_pars(nzf, head)
# Is not a par2file or nothing to do
else:
pass
# No filename in seg 1? Probably not uu or yenc encoded
# Set subject as filename
else:
# Not enough
return False
nzf.filename = nzf.subject
@synchronized(NZO_LOCK)
def remove_article(self, article, found):
@@ -1112,6 +1064,9 @@ class NzbObject(TryList):
logging.debug('Abort job "%s", due to impossibility to complete it', self.final_name_pw_clean)
return True, True
if file_done:
self.handle_par2(nzf, file_done)
if not found:
# Add extra parfiles when there was a damaged article and not pre-checking
if self.extrapars and not self.precheck:
@@ -1121,14 +1076,6 @@ class NzbObject(TryList):
if self.bad_articles > MAX_BAD_ARTICLES:
self.abort_direct_unpacker()
# Increase missing bytes counter
self.bytes_missing += article.bytes
else:
# Increase counter of actually finished bytes
self.bytes_downloaded += article.bytes
# All the bytes that were tried
self.bytes_tried += article.bytes
post_done = False
if not self.files:
post_done = True
@@ -1162,8 +1109,8 @@ class NzbObject(TryList):
# Looking for the longest name first, minimizes the chance on a mismatch
files.sort(lambda x, y: len(y) - len(x))
# The NZFs should be tried shortest first, to improve the chance on a proper match
nzfs = self.files[:]
# The NZFs should be tried shortest first, to improve the chance on a proper match
nzfs.sort(lambda x, y: len(x.subject) - len(y.subject))
# Flag files from NZB that already exist as finished
@@ -1173,43 +1120,28 @@ class NzbObject(TryList):
if (nzf.filename == filename) or (subject == filename) or (filename in subject):
nzf.filename = filename
nzf.bytes_left = 0
self.handle_par2(nzf, file_done=True)
self.remove_nzf(nzf)
nzfs.remove(nzf)
files.remove(filename)
# Set bytes correctly
self.bytes_tried += nzf.bytes
self.bytes_downloaded += nzf.bytes
# Process par2 files
filepath = os.path.join(wdir, filename)
if sabnzbd.par2file.is_parfile(filepath):
self.handle_par2(nzf, filepath)
break
# Create an NZF for each remaining existing file
try:
# Create an NZF for each remaining existing file
for filename in files:
# Create NZB's using basic information
filepath = os.path.join(wdir, filename)
if os.path.exists(filepath):
tup = os.stat(filepath)
tm = datetime.datetime.fromtimestamp(tup.st_mtime)
nzf = NzbFile(tm, filename, [], tup.st_size, self)
self.files.append(nzf)
self.files_table[nzf.nzf_id] = nzf
nzf.filename = filename
self.remove_nzf(nzf)
# Set bytes correctly
self.bytes += nzf.bytes
self.bytes_tried += nzf.bytes
self.bytes_downloaded += nzf.bytes
# Process par2 files
if sabnzbd.par2file.is_parfile(filepath):
self.handle_par2(nzf, filepath)
logging.info('Existing file %s added to job', filename)
tup = os.stat(os.path.join(wdir, filename))
tm = datetime.datetime.fromtimestamp(tup.st_mtime)
nzf = NzbFile(tm, '"%s"' % filename, [], tup.st_size, self)
self.files.append(nzf)
self.files_table[nzf.nzf_id] = nzf
self.bytes += nzf.bytes
nzf.filename = filename
nzf.bytes_left = 0
self.handle_par2(nzf, file_done=True)
self.remove_nzf(nzf)
logging.info('File %s added to job', filename)
except:
logging.debug('Bad NZB handling')
logging.info("Traceback: ", exc_info=True)
@@ -1297,19 +1229,14 @@ class NzbObject(TryList):
@synchronized(NZO_LOCK)
def add_parfile(self, parfile):
""" Add parfile to the files to be downloaded
Resets trylist just to be sure
Adjust download-size accordingly
"""
if not parfile.completed and parfile not in self.files and parfile not in self.finished_files:
parfile.reset_all_try_lists()
if not parfile.completed and parfile not in self.files:
self.files.append(parfile)
self.bytes_tried -= parfile.bytes_left
if parfile.extrapars and parfile in parfile.extrapars:
parfile.extrapars.remove(parfile)
self.remove_extrapar(parfile)
@synchronized(NZO_LOCK)
def remove_parset(self, setname):
if setname in self.extrapars:
self.extrapars.pop(setname)
if setname in self.partable:
self.partable.pop(setname)
@@ -1319,28 +1246,38 @@ class NzbObject(TryList):
for _set in self.extrapars:
if parfile in self.extrapars[_set]:
self.extrapars[_set].remove(parfile)
if self.partable and _set in self.partable and self.partable[_set] and parfile in self.partable[_set].extrapars:
self.partable[_set].extrapars.remove(parfile)
@synchronized(NZO_LOCK)
def prospective_add(self, nzf):
""" Add par2 files to compensate for missing articles
This fails in case of multi-sets with identical setnames
"""
# How many do we already have?
blocks_already = 0
for nzf_check in self.files:
# Only par2 files have a blocks attribute
if nzf_check.blocks:
blocks_already = blocks_already + int_conv(nzf_check.blocks)
# Make sure to also select a parset if it was in the original filename
original_filename = self.renames.get(nzf.filename, '')
# Get some blocks!
if not nzf.is_par2:
# Need more?
if not nzf.is_par2 and blocks_already < self.bad_articles:
# We have to find the right par-set
blocks_new = 0
for parset in self.extrapars.keys():
if (parset in nzf.filename or parset in original_filename) and self.extrapars[parset]:
for new_nzf in self.extrapars[parset]:
extrapars_sorted = sorted(self.extrapars[parset], key=lambda x: x.blocks, reverse=True)
# Loop until we have enough
while blocks_already < self.bad_articles and extrapars_sorted:
new_nzf = extrapars_sorted.pop()
# Reset NZF TryList, in case something was on it before it became extrapar
new_nzf.reset_all_try_lists()
self.add_parfile(new_nzf)
blocks_new += int_conv(new_nzf.blocks)
# Enough now?
if blocks_new >= self.bad_articles:
logging.info('Prospectively added %s repair blocks to %s', blocks_new, self.final_name)
break
self.extrapars[parset] = extrapars_sorted
blocks_already = blocks_already + int_conv(new_nzf.blocks)
logging.info('Prospectively added %s repair blocks to %s', new_nzf.blocks, self.final_name)
# Reset NZO TryList
self.reset_try_list()
@@ -1438,9 +1375,20 @@ class NzbObject(TryList):
self.nzo_info[type] += 1
self.bad_articles += 1
def server_allowed(self, server):
if not server.categories:
return False
if "Default" in server.categories:
return True
if self.cat in server.categories:
return True
# There are no default servers, and either we have no category, or no server matches our category
return False
def get_article(self, server, servers):
article = None
nzf_remove_list = []
tried_all_articles = True
for nzf in self.files:
if nzf.deleted:
@@ -1464,17 +1412,21 @@ class NzbObject(TryList):
article = nzf.get_article(server, servers)
if article:
break
if article == None:
# None is returned by NZF when server is not tried for all articles
tried_all_articles = False
# Remove all files for which admin could not be read
for nzf in nzf_remove_list:
nzf.deleted = True
nzf.completed = True
self.files.remove(nzf)
# If cleanup emptied the active files list, end this job
if nzf_remove_list and not self.files:
sabnzbd.NzbQueue.do.end_job(self)
if not article:
# Only add to trylist when server has been tried for all articles of all NZF's
if not article and tried_all_articles:
# No articles for this server, block for next time
self.add_to_try_list(server)
return article
@@ -1626,11 +1578,6 @@ class NzbObject(TryList):
else:
return None
@property
def remaining(self):
""" Return remaining bytes """
return self.bytes - self.bytes_tried
@synchronized(NZO_LOCK)
def purge_data(self, keep_basic=False, del_files=False):
""" Remove all admin info, 'keep_basic' preserves attribs and nzb """
@@ -1661,20 +1608,35 @@ class NzbObject(TryList):
except:
pass
def remaining(self):
""" Return remaining bytes """
bytes_par2 = 0
for _set in self.extrapars:
for nzf in self.extrapars[_set]:
bytes_par2 += nzf.bytes_left
# Subtract PAR2 sets and already downloaded bytes
bytes_left = self.bytes - self.bytes_tried - bytes_par2
return bytes_left
def gather_info(self, full=False):
queued_files = []
if full:
for _set in self.extrapars:
for nzf in self.extrapars[_set]:
# Don't show files twice
if not nzf.completed and nzf not in self.files:
queued_files.append(nzf)
nzf.setname = _set
queued_files.append(nzf)
return PNFO(self.repair, self.unpack, self.delete, self.script, self.nzo_id,
self.final_name_labeled, self.password, {}, '', self.cat, self.url, self.remaining,
self.bytes, self.avg_stamp, self.avg_date, self.finished_files if full else [],
self.files if full else [], queued_files, self.status, self.priority,
self.bytes_missing, self.direct_unpacker.get_formatted_stats() if self.direct_unpacker else 0)
return PNFO(self.repair, self.unpack, self.delete, self.script,
self.nzo_id, self.final_name_labeled, self.password, {},
'', self.cat, self.url,
self.remaining(), self.bytes, self.avg_stamp, self.avg_date,
self.finished_files if full else [],
self.files if full else [],
queued_files,
self.status, self.priority,
self.nzo_info.get('missing_articles', 0),
self.bytes_tried - self.bytes_downloaded,
self.direct_unpacker.get_formatted_stats() if self.direct_unpacker else 0)
def get_nzf_by_id(self, nzf_id):
if nzf_id in self.files_table:
@@ -1740,16 +1702,14 @@ class NzbObject(TryList):
where "res" is True when this is a duplicate
where "series" is True when this is an episode
"""
series = False
no_dupes = cfg.no_dupes()
no_series_dupes = cfg.no_series_dupes()
series_propercheck = cfg.series_propercheck()
# abort logic if dupe check is off for both nzb+series
if not no_dupes and not no_series_dupes:
return False, False
series = False
res = False
history_db = HistoryDB()
@@ -1762,12 +1722,10 @@ class NzbObject(TryList):
logging.debug('Dupe checking NZB against backup: filename=%s, result=%s', self.filename, res)
# dupe check off nzb filename
if not res and no_series_dupes:
series, season, episode, misc = sabnzbd.newsunpack.analyse_show(self.final_name)
if RE_PROPER.match(misc) and series_propercheck:
logging.debug('Dupe checking series+season+ep in history aborted due to PROPER/REAL/REPACK found')
else:
res = history_db.have_episode(series, season, episode)
logging.debug('Dupe checking series+season+ep in history: series=%s, season=%s, episode=%s, result=%s', series, season, episode, res)
series, season, episode, dummy = sabnzbd.newsunpack.analyse_show(self.final_name)
res = history_db.have_episode(series, season, episode)
series = res
logging.debug('Dupe checking series+season+ep in history: series=%s, season=%s, episode=%s, result=%s', series, season, episode, res)
history_db.close()
return res, series
@@ -1780,21 +1738,17 @@ class NzbObject(TryList):
""" Save to pickle file, selecting attributes """
dict_ = {}
for item in NzbObjectSaver:
dict_[item] = getattr(self, item)
dict_['try_list'] = TryList.__getstate__(self)
dict_[item] = self.__dict__[item]
return dict_
def __setstate__(self, dict_):
""" Load from pickle file, selecting attributes """
for item in NzbObjectSaver:
try:
setattr(self, item, dict_[item])
self.__dict__[item] = dict_[item]
except KeyError:
# Handle new attributes
setattr(self, item, None)
TryList.__setstate__(self, dict_.get('try_list', []))
# Set non-transferable values
self.__dict__[item] = None
self.pp_active = False
self.avg_stamp = time.mktime(self.avg_date.timetuple())
self.wait = None
@@ -1810,8 +1764,6 @@ class NzbObject(TryList):
self.renames = {}
if self.bad_articles is None:
self.bad_articles = 0
if self.bytes_missing is None:
self.bytes_missing = 0
if self.bytes_tried is None:
# Fill with old info
self.bytes_tried = 0
@@ -1820,6 +1772,7 @@ class NzbObject(TryList):
self.bytes_tried += nzf.bytes
for nzf in self.files:
self.bytes_tried += nzf.bytes - nzf.bytes_left
TryList.__init__(self)
def __repr__(self):
return "<NzbObject: filename=%s>" % self.filename
@@ -1973,6 +1926,26 @@ def set_attrib_file(path, attribs):
f.close()
def analyse_par2(name):
""" Check if file is a par2-file and determine vol/block
return head, vol, block
head is empty when not a par2 file
"""
head = None
vol = block = 0
if name and not REJECT_PAR2_RE.search(name):
m = PROBABLY_PAR2_RE.search(name)
if m:
head = m.group(1)
vol = m.group(2)
block = m.group(3)
elif name.lower().find('.par2') > 0:
head = os.path.splitext(name)[0].strip()
else:
head = None
return head, vol, block
def name_extractor(subject):
""" Try to extract a file name from a subject line, return `subject` if in doubt """
result = subject

View File

@@ -241,10 +241,10 @@ def error_page_404(status, message, traceback, version):
<head>
<script type="text/javascript">
<!--
location.href = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '') + '%s' ;
location.href = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '') + '/sabnzbd/' ;
//-->
</script>
</head>
<body><br/></body>
</html>
''' % cfg.url_base()
'''

View File

@@ -1,166 +0,0 @@
#!/usr/bin/python -OO
# Copyright 2008-2017 The SABnzbd-Team <team@sabnzbd.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
sabnzbd.par2file - All par2-related functionality
"""
import os
import logging
import re
import hashlib
import struct
PROBABLY_PAR2_RE = re.compile(r'(.*)\.vol(\d*)[\+\-](\d*)\.par2', re.I)
PAR_ID = "PAR2\x00PKT"
PAR_RECOVERY_ID = "RecvSlic"
def is_parfile(filename):
""" Check quickly whether file has par2 signature """
try:
with open(filename, "rb") as f:
buf = f.read(8)
return buf.startswith(PAR_ID)
except:
pass
return False
def analyse_par2(name, filepath=None):
""" Check if file is a par2-file and determine vol/block
return setname, vol, block
setname is empty when not a par2 file
"""
name = name.strip()
setname = None
vol = block = 0
m = PROBABLY_PAR2_RE.search(name)
if m:
setname = m.group(1)
vol = m.group(2)
block = m.group(3)
else:
# Base-par2 file
setname = os.path.splitext(name)[0].strip()
# Could not parse the filename, need deep inspection
# We already know it's a par2 from the is_parfile
if filepath:
try:
# Quick loop to find number blocks
# Assumes blocks are larger than 128 bytes
# Worst case, we only count 1, still good
with open(filepath, "rb") as f:
buf = f.read(128)
while buf:
if PAR_RECOVERY_ID in buf:
block += 1
buf = f.read(128)
except:
pass
return setname, vol, block
def parse_par2_file(nzf, fname):
""" Get the hash table and the first-16k hash table from a PAR2 file
Return as dictionary, indexed on names or hashes for the first-16 table
For a full description of the par2 specification, visit:
http://parchive.sourceforge.net/docs/specifications/parity-volume-spec/article-spec.html
"""
table = {}
duplicates16k = []
try:
f = open(fname, 'rb')
except:
return table
try:
header = f.read(8)
while header:
name, hash, hash16k = parse_par2_file_packet(f, header)
if name:
table[name] = hash
if hash16k not in nzf.nzo.md5of16k:
nzf.nzo.md5of16k[hash16k] = name
elif nzf.nzo.md5of16k[hash16k] != name:
# Not unique and not already linked to this file
# Remove to avoid false-renames
duplicates16k.append(hash16k)
header = f.read(8)
except (struct.error, IndexError):
logging.info('Cannot use corrupt par2 file for QuickCheck, "%s"', fname)
logging.info('Traceback: ', exc_info=True)
table = {}
except:
logging.debug('QuickCheck parser crashed in file %s', fname)
logging.info('Traceback: ', exc_info=True)
table = {}
f.close()
# Have to remove duplicates at the end to make sure
# no trace is left in case of multi-duplicates
for hash16k in duplicates16k:
if hash16k in nzf.nzo.md5of16k:
old_name = nzf.nzo.md5of16k.pop(hash16k)
logging.debug('Par2-16k signature of %s not unique, discarding', old_name)
return table
def parse_par2_file_packet(f, header):
""" Look up and analyze a FileDesc package """
nothing = None, None, None
if header != PAR_ID:
return nothing
# Length must be multiple of 4 and at least 20
len = struct.unpack('<Q', f.read(8))[0]
if int(len / 4) * 4 != len or len < 20:
return nothing
# Next 16 bytes is md5sum of this packet
md5sum = f.read(16)
# Read and check the data
data = f.read(len - 32)
md5 = hashlib.md5()
md5.update(data)
if md5sum != md5.digest():
return nothing
# The FileDesc packet looks like:
# 16 : "PAR 2.0\0FileDesc"
# 16 : FileId
# 16 : Hash for full file **
# 16 : Hash for first 16K
# 8 : File length
# xx : Name (multiple of 4, padded with \0 if needed) **
# See if it's the right packet and get name + hash
for offset in range(0, len, 8):
if data[offset:offset + 16] == "PAR 2.0\0FileDesc":
hash = data[offset + 32:offset + 48]
hash16k = data[offset + 48:offset + 64]
filename = data[offset + 72:].strip('\0')
return filename, hash, hash16k
return nothing

View File

@@ -614,6 +614,18 @@ def prepare_extraction_path(nzo):
return tmp_workdir_complete, workdir_complete, file_sorter, one_folder, marker_file
def is_parfile(fn):
""" Check quickly whether file has par2 signature """
PAR_ID = "PAR2\x00PKT"
try:
with open(fn, "rb") as f:
buf = f.read(8)
return buf.startswith(PAR_ID)
except:
pass
return False
def parring(nzo, workdir):
""" Perform par processing. Returns: (par_error, re_add) """
filename = nzo.final_name
@@ -622,7 +634,7 @@ def parring(nzo, workdir):
# Get verification status of sets
verified = sabnzbd.load_data(VERIFIED_FILE, nzo.workpath, remove=False) or {}
repair_sets = nzo.extrapars.keys()
repair_sets = nzo.partable.keys()
re_add = False
par_error = False
@@ -635,9 +647,7 @@ def parring(nzo, workdir):
if not verified.get(setname, False):
logging.info("Running verification and repair on set %s", setname)
parfile_nzf = nzo.partable[setname]
# Check if file maybe wasn't deleted and if we maybe have more files in the parset
if os.path.exists(os.path.join(nzo.downpath, parfile_nzf.filename)) or nzo.extrapars[setname]:
if os.path.exists(os.path.join(nzo.downpath, parfile_nzf.filename)) or parfile_nzf.extrapars:
need_re_add, res = par2_repair(parfile_nzf, nzo, workdir, setname, single=single)
# Was it aborted?
@@ -651,19 +661,51 @@ def parring(nzo, workdir):
else:
continue
par_error = par_error or not res
else:
# We must not have found any par2..
logging.info("No par2 sets for %s", filename)
nzo.set_unpack_info('Repair', T('[%s] No par2 sets') % unicoder(filename))
if cfg.sfv_check() and not verified.get('', False):
par_error = not try_sfv_check(nzo, workdir, '')
verified[''] = not par_error
# If still no success, do RAR-check
if not par_error and cfg.enable_unrar():
par_error = not try_rar_check(nzo, workdir, '')
verified[''] = not par_error
# Obfuscated par2 check
logging.info('No par2 sets found, running obfuscated check on %s', filename)
# Get the NZF's and sort them based on size
nzfs_sorted = sorted(nzo.finished_files, key=lambda x: x.bytes)
# We will have to make 'fake' par files that are recognized
par2_vol = 0
par2_filename = None
for nzf_try in nzfs_sorted:
# run through list of files, looking for par2 signature..
logging.debug("Checking par2 signature of %s", nzf_try.filename)
try:
nzf_path = os.path.join(workdir, nzf_try.filename)
if(is_parfile(nzf_path)):
# We need 1 base-name so they are recognized as 1 set
if not par2_filename:
par2_filename = nzf_path
# Rename so handle_par2() picks it up
newpath = '%s.vol%d+%d.par2' % (par2_filename, par2_vol, par2_vol + 1)
renamer(nzf_path, newpath)
nzf_try.filename = os.path.split(newpath)[1]
# Let the magic happen
nzo.handle_par2(nzf_try, file_done=True)
par2_vol += 1
except:
pass
if par2_vol > 0:
# Pars found, we do it again
par_error, re_add = parring(nzo, workdir)
else:
# We must not have found any par2..
logging.info("No par2 sets for %s", filename)
nzo.set_unpack_info('Repair', T('[%s] No par2 sets') % unicoder(filename))
if cfg.sfv_check() and not verified.get('', False):
par_error = not try_sfv_check(nzo, workdir, '')
verified[''] = not par_error
# If still no success, do RAR-check
if not par_error and cfg.enable_unrar():
par_error = not try_rar_check(nzo, workdir, '')
verified[''] = not par_error
if re_add:
logging.info('Re-added %s to queue', filename)
if nzo.priority != TOP_PRIORITY:

View File

@@ -172,13 +172,26 @@ class RSSQueue(object):
self.shutdown = False
try:
self.jobs = sabnzbd.load_admin(RSS_FILE_NAME)
if self.jobs:
for feed in self.jobs:
defined = config.get_rss().keys()
feeds = sabnzbd.load_admin(RSS_FILE_NAME)
if type(feeds) == type({}):
for feed in feeds:
if feed not in defined:
logging.debug('Dropping obsolete data for feed "%s"', feed)
continue
self.jobs[feed] = {}
for link in feeds[feed]:
# Consistency check on data
try:
item = feeds[feed][link]
if not isinstance(item, dict) or not isinstance(item.get('title'), unicode):
raise IndexError
self.jobs[feed][link] = item
except (KeyError, IndexError):
logging.info('Incorrect entry in %s detected, discarding %s', RSS_FILE_NAME, item)
remove_obsolete(self.jobs[feed], self.jobs[feed].keys())
except:
logging.warning(T('Cannot read %s'), RSS_FILE_NAME)
logging.info("Traceback: ", exc_info=True)
except IOError:
logging.debug('Cannot read file %s', RSS_FILE_NAME)
# jobs is a NAME-indexed dictionary
# Each element is link-indexed dictionary
@@ -194,6 +207,7 @@ class RSSQueue(object):
# script : script
# prio : priority
# time : timestamp (used for time-based clean-up)
# order : order in the RSS feed
# size : size in bytes
# age : age in datetime format as specified by feed
# season : season number (if applicable)
@@ -307,18 +321,18 @@ class RSSQueue(object):
all_entries.extend(entries)
entries = all_entries
# In case of a new feed
if feed not in self.jobs:
self.jobs[feed] = {}
jobs = self.jobs[feed]
# Error in readout or now new readout
if readout:
if not entries:
return unicoder(msg)
else:
entries = jobs.keys()
# Sort in the order the jobs came from the feed
entries.sort(lambda x, y: jobs[x].get('order', 0) - jobs[y].get('order', 0))
order = 0
# Filter out valid new links
for entry in entries:
if self.shutdown:
@@ -384,7 +398,7 @@ class RSSQueue(object):
episode = int_conv(episode)
# Match against all filters until an positive or negative match
logging.debug('Size %s', size)
logging.debug('Size %s for %s', size, title)
for n in xrange(regcount):
if reEnabled[n]:
if category and reTypes[n] == 'C':
@@ -480,13 +494,14 @@ class RSSQueue(object):
else:
star = first
if result:
_HandleLink(jobs, feed, link, title, size, age, season, episode, 'G', category, myCat, myPP,
myScript, act, star, priority=myPrio, rule=str(n))
_HandleLink(jobs, link, title, size, age, season, episode, 'G', category, myCat, myPP, myScript,
act, star, order, priority=myPrio, rule=str(n))
if act:
new_downloads.append(title)
else:
_HandleLink(jobs, feed, link, title, size, age, season, episode, 'B', category, myCat, myPP,
myScript, False, star, priority=myPrio, rule=str(n))
_HandleLink(jobs, link, title, size, age, season, episode, 'B', category, myCat, myPP, myScript,
False, star, order, priority=myPrio, rule=str(n))
order += 1
# Send email if wanted and not "forced"
if new_downloads and cfg.email_rss() and not force:
@@ -586,8 +601,8 @@ class RSSQueue(object):
return ''
def _HandleLink(jobs, feed, link, title, size, age, season, episode, flag, orgcat, cat, pp, script,
download, star, priority=NORMAL_PRIORITY, rule=0):
def _HandleLink(jobs, link, title, size, age, season, episode, flag, orgcat, cat, pp, script, download, star,
order, priority=NORMAL_PRIORITY, rule=0):
""" Process one link """
if script == '':
script = None
@@ -601,6 +616,7 @@ def _HandleLink(jobs, feed, link, title, size, age, season, episode, flag, orgca
jobs[link]['pp'] = pp
jobs[link]['script'] = script
jobs[link]['prio'] = str(priority)
jobs[link]['order'] = order
jobs[link]['orgcat'] = orgcat
jobs[link]['size'] = size
jobs[link]['age'] = age
@@ -618,7 +634,7 @@ def _HandleLink(jobs, feed, link, title, size, age, season, episode, flag, orgca
jobs[link]['status'] = 'D'
jobs[link]['time_downloaded'] = time.localtime()
logging.info("Adding %s (%s) to queue", link, title)
sabnzbd.add_url(link, pp=pp, script=script, cat=cat, priority=priority, nzbname=nzbname, feed_name=feed)
sabnzbd.add_url(link, pp=pp, script=script, cat=cat, priority=priority, nzbname=nzbname)
else:
if star:
jobs[link]['status'] = flag + '*'

View File

@@ -152,12 +152,6 @@ def init():
elif action_name == 'resume_all_high':
action = sabnzbd.nzbqueue.NzbQueue.do.resume_on_prio
arguments = [HIGH_PRIORITY]
elif action_name == 'pause_cat':
action = sabnzbd.nzbqueue.NzbQueue.do.pause_on_cat
arguments = [argument_list]
elif action_name == 'resume_cat':
action = sabnzbd.nzbqueue.NzbQueue.do.resume_on_cat
arguments = [argument_list]
else:
logging.warning(T('Unknown action: %s'), action_name)
continue
@@ -276,7 +270,6 @@ def sort_schedules(all_events, now=None):
for schedule in cfg.schedules():
parms = None
try:
# Note: the last parameter can have spaces (category name)!
enabled, m, h, dd, action, parms = schedule.split(None, 5)
except:
try:

View File

@@ -73,8 +73,6 @@ SKIN_TEXT = {
'sch-resume_all_high': TT('Resume high prioirty jobs'), #: Config->Scheduler
'sch-enable_quota' : TT('Enable quota management'), #: Config->Scheduler
'sch-disable_quota' : TT('Disable quota management'), #: Config->Scheduler
'sch-pause_cat' : TT('Pause jobs with category'), #: Config->Scheduler
'sch-resume_cat' : TT('Resume jobs with category'), #: Config->Scheduler
'prowl-off' : TT('Off'), #: Prowl priority
'prowl-very-low' : TT('Very Low'), #: Prowl priority
@@ -446,8 +444,6 @@ SKIN_TEXT = {
'explain-no_dupes' : TT('Detect identical NZB files (based on items in your History or files in .nzb Backup Folder)'),
'opt-no_series_dupes' : TT('Detect duplicate episodes in series'),
'explain-no_series_dupes' : TT('Detect identical episodes in series (based on "name/season/episode" of items in your History)'),
'opt-series_propercheck' : TT('Allow proper releases'),
'explain-series_propercheck' : TT('Bypass series duplicate detection if PROPER, REAL or REPACK is detected in the download name'),
'nodupes-off' : TT('Off'), #: Three way switch for duplicates
'nodupes-ignore' : TT('Discard'), #: Four way switch for duplicates
'nodupes-pause' : TT('Pause'), #: Four way switch for duplicates
@@ -856,7 +852,6 @@ SKIN_TEXT = {
'Glitter-pausePrompt': TT('How long or untill when do you want to pause? (in English!)'),
'Glitter-pausePromptFail': TT('Sorry, we could not interpret that. Try again.'),
'Glitter-pauseFor' : TT('Pause for...'),
'Glitter-refresh' : TT('Refresh'),
'Glitter-sortAgeAsc' : TT('Sort by Age <small>Oldest&rarr;Newest</small>'),
'Glitter-sortAgeDesc' : TT('Sort by Age <small>Newest&rarr;Oldest</small>'),
'Glitter-sortNameAsc' : TT('Sort by Name <small>A&rarr;Z</small>'),
@@ -999,6 +994,5 @@ SABnzbd comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under certain conditions.
It is licensed under the GNU GENERAL PUBLIC LICENSE Version 2 or (at your option) any later version.
'''),
'wizard-ad-1': TT('In order to download from usenet you will require access to a provider. Your ISP may provide you with access, however a premium provider is recommended.'),
'wizard-ad-2': TT('Don\'t have a usenet provider? We recommend trying %s.'),
'wizard-ad': TT('In order to download from Usenet you will require access to a provider. Your ISP may provide you with access, however a premium provider is recommended. Don\'t have a Usenet provider? We recommend trying %s.''')
}

View File

@@ -78,18 +78,11 @@ class URLGrabber(Thread):
if not url:
# stop signal, go test self.shutdown
continue
if future_nzo:
if future_nzo and future_nzo.wait and future_nzo.wait > time.time():
# Re-queue when too early and still active
if future_nzo.wait and future_nzo.wait > time.time():
self.add(url, future_nzo)
continue
# Paused
if future_nzo.status == Status.PAUSED:
self.add(url, future_nzo)
time.sleep(1.0)
continue
self.add(url, future_nzo)
continue
url = url.replace(' ', '')
try:

View File

@@ -76,11 +76,11 @@ RESOLVE_RELATIVE_URIS = 1
# If you want feedparser to automatically sanitize all potentially unsafe
# HTML content, set this to 1.
SANITIZE_HTML = 0
SANITIZE_HTML = 1
# If you want feedparser to automatically parse microformat content embedded
# in entry contents, set this to 1
PARSE_MICROFORMATS = 0
PARSE_MICROFORMATS = 1
# ---------- Python 3 modules (make it work if possible) ----------
try:

78
sabnzbd/utils/sslinfo.py Normal file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/python -OO
# Copyright 2008-2017 The SABnzbd-Team <team@sabnzbd.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
sabnzbd.utils.sslinfo - Information on the system's SSL setup
"""
# v23 indicates "negotiate highest possible"
_ALL_PROTOCOLS = ('v23', 't12', 't11', 't1', 'v3', 'v2')
_SSL_PROTOCOLS = {}
_SSL_PROTOCOLS_LABELS = []
try:
import ssl
# Basic
_SSL_PROTOCOLS['v23'] = ssl.PROTOCOL_SSLv23
# Loop through supported versions
for ssl_prop in dir(ssl):
if ssl_prop.startswith('PROTOCOL_'):
if ssl_prop.endswith('SSLv2'):
_SSL_PROTOCOLS['v2'] = ssl.PROTOCOL_SSLv2
_SSL_PROTOCOLS_LABELS.append('SSL v2')
elif ssl_prop.endswith('SSLv3'):
_SSL_PROTOCOLS['v3'] = ssl.PROTOCOL_SSLv3
_SSL_PROTOCOLS_LABELS.append('SSL v3')
elif ssl_prop.endswith('TLSv1'):
_SSL_PROTOCOLS['t1'] = ssl.PROTOCOL_TLSv1
_SSL_PROTOCOLS_LABELS.append('TLS v1')
elif ssl_prop.endswith('TLSv1_1'):
_SSL_PROTOCOLS['t11'] = ssl.PROTOCOL_TLSv1_1
_SSL_PROTOCOLS_LABELS.append('TLS v1.1')
elif ssl_prop.endswith('TLSv1_2'):
_SSL_PROTOCOLS['t12'] = ssl.PROTOCOL_TLSv1_2
_SSL_PROTOCOLS_LABELS.append('TLS v1.2')
# Reverse the labels, SSL's always come first in the dir()
_SSL_PROTOCOLS_LABELS.reverse()
except:
pass
def ssl_protocols():
''' Return acronyms for SSL protocols '''
return _SSL_PROTOCOLS.keys()
def ssl_protocols_labels():
''' Return human readable labels for SSL protocols, highest quality first '''
return _SSL_PROTOCOLS_LABELS
def ssl_version():
try:
import ssl
return ssl.OPENSSL_VERSION
except (ImportError, AttributeError):
return None
if __name__ == '__main__':
print 'SSL version: %s' % ssl_version()
print 'Supported protocols: %s' % ssl_protocols()

View File

@@ -4,5 +4,5 @@
# You MUST use double quotes (so " and not ')
__version__ = "2.3.0-develop"
__baseline__ = "unknown"
__version__ = "2.2.1"
__baseline__ = "bcc4dd75cfeab41a4b56308709c04fd568859d1f"

View File

@@ -229,19 +229,19 @@ class Wizard(object):
for sock in socks:
if sock:
if cfg.enable_https() and cfg.https_port():
url = 'https://%s:%s%s' % (sock, cfg.https_port(), cfg.url_base())
url = 'https://%s:%s/sabnzbd/' % (sock, cfg.https_port())
elif cfg.enable_https():
url = 'https://%s:%s%s' % (sock, cfg.cherryport(), cfg.url_base())
url = 'https://%s:%s/sabnzbd/' % (sock, cfg.cherryport())
else:
url = 'http://%s:%s%s' % (sock, cfg.cherryport(), cfg.url_base())
url = 'http://%s:%s/sabnzbd/' % (sock, cfg.cherryport())
urls.append(url)
if cfg.enable_https() and cfg.https_port():
access_url = 'https://%s:%s%s' % (sock, cfg.https_port(), cfg.url_base())
access_url = 'https://%s:%s/sabnzbd/' % (sock, cfg.https_port())
elif cfg.enable_https():
access_url = 'https://%s:%s%s' % (access_uri, cfg.cherryport(), cfg.url_base())
access_url = 'https://%s:%s/sabnzbd/' % (access_uri, cfg.cherryport())
else:
access_url = 'http://%s:%s%s' % (access_uri, cfg.cherryport(), cfg.url_base())
access_url = 'http://%s:%s/sabnzbd/' % (access_uri, cfg.cherryport())
return access_url, urls

View File

@@ -84,7 +84,8 @@ def set_bonjour(host=None, port=None):
suffix = ''
else:
suffix = '.local'
if hasattr(cherrypy.wsgiserver, 'redirect_url'):
cherrypy.wsgiserver.redirect_url("https://%s%s:%s/sabnzbd" % (name, suffix, port))
logging.debug('Try to publish in Bonjour as "%s" (%s:%s)', name, host, port)
try:
refObject = pybonjour.DNSServiceRegister(
@@ -94,15 +95,12 @@ def set_bonjour(host=None, port=None):
domain=domain,
host=zhost,
port=int(port),
txtRecord=pybonjour.TXTRecord({'path': cfg.url_base(),
txtRecord=pybonjour.TXTRecord({'path': '/sabnzbd/',
'https': cfg.enable_https()}),
callBack=_zeroconf_callback)
except sabnzbd.utils.pybonjour.BonjourError as e:
except sabnzbd.utils.pybonjour.BonjourError:
_BONJOUR_OBJECT = None
logging.debug('Failed to start Bonjour service: %s', str(e))
except:
_BONJOUR_OBJECT = None
logging.debug('Failed to start Bonjour service due to non-pybonjour related problem', exc_info=True)
logging.debug('Failed to start Bonjour service')
else:
Thread(target=_bonjour_server, args=(refObject,))
_BONJOUR_OBJECT = refObject

View File

@@ -1,15 +1,15 @@
@echo off
rem Example of a post processing script for SABnzbd
echo.
echo Running in directory "%~d0%~p0"
echo.
echo The first parameter (result-dir) = %1
echo The second parameter (nzb-name) = %2
echo The third parameter (nice name) = %3
echo The fourth parameter (newzbin #) = %4
echo The fifth parameter (category) = %5
echo The sixth parameter (group) = %6
echo The seventh parameter (status) = %7
echo The eight parameter (failure_url)= %8
echo.
@echo off
rem Example of a post processing script for SABnzbd
echo.
echo Running in directory "%~d0%~p0"
echo.
echo The first parameter (result-dir) = %1
echo The second parameter (nzb-name) = %2
echo The third parameter (nice name) = %3
echo The fourth parameter (newzbin #) = %4
echo The fifth parameter (category) = %5
echo The sixth parameter (group) = %6
echo The seventh parameter (status) = %7
echo The eight parameter (failure_url)= %8
echo.