1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

..

165 Commits

Author SHA1 Message Date
Devin Buhl e3dc31cca5 Try to add year to release titles that have no year (foriegn release groups) (#1028) 2017-03-05 17:58:48 -05:00
Devin Buhl ddc6ccbf15 Added new TestCase for Parser and fixed spelling error 2017-03-05 14:32:51 -05:00
Mitchell Cash 18773bc665 Fixed: Delay profiles are no longer hidden under advanced settings (#1019) 2017-03-05 10:43:42 -05:00
Devin Buhl 91c71ed6a0 Revert "Added FindByAlternativeTitle in MovieRepo."
It won't work. You have to do it like in FindByTitle. This reverts commit 0d85c7435c.
2017-03-05 10:47:45 +01:00
geogolem f3b5d9a1d6 Merge pull request #1018 from geogolem/useRequestBuilderTrakt
use http request builder (aided by onedrop)
2017-03-05 03:50:12 -05:00
geogolem 83deba1c99 use http request builder (aided by onedrop) 2017-03-05 03:45:20 -05:00
Mitchell Cash 9787cf6cdd Improve indexer health check messages (#1015)
* Improve indexer health check messages

Fixed: Improve health check message when all enabled indexers are disabled due to failures
Closes #1551

* Fixed: Health check failing and preventing others from running

* Fixed Indexer Health Checks and tests.
2017-03-05 02:50:45 -05:00
Mitchell Cash 7433e89467 Clean RSS feed before detecting type (#1014) 2017-03-05 02:48:48 -05:00
geogolem 0aa6066a6f Merge pull request #1016 from geogolem/exclusionsFix
Exclusions fix
2017-03-05 02:07:47 -05:00
geogolem 3dd14c72c8 store titleSlug in tags for exclusions and always use TMDBID 2017-03-05 02:02:24 -05:00
Devin Buhl 0d85c7435c Added FindByAlternativeTitle in MovieRepo. 2017-03-04 23:47:45 -05:00
geogolem a3c0f4cb3f also use TMDBID on list sync 2017-03-04 21:55:11 -05:00
geogolem 3f2da1441f always check exclusions with tmdbid 2017-03-04 21:55:11 -05:00
geogolem f49d68ad6a Merge pull request #778 from geogolem/traktAuthentication
fully functional traktAuthentication
2017-03-04 21:47:43 -05:00
geogolem f138d4f677 an updated radarrAPI has been deployed --> this commit makes
trakt authentication ready to be merged to the develop branch
2017-03-04 21:45:22 -05:00
geogolem aa977eb2d5 fully functional traktAuthentication
using api.couchpota.to with comments
for when updated RadarrAPI is deployed
2017-03-04 17:51:21 -05:00
Devin Buhl e0f72e4853 Fix error with null dates 2017-03-04 13:42:29 -05:00
Devin Buhl 83560ad937 Patch/more updates (#1009)
* add downloaded quality to cut off

* set profile to 1 on model too

* get the lowest year in release dates
2017-03-04 13:25:04 -05:00
Leonardo Galli 5cace1d857 Added debug messages to check quality. 2017-03-04 18:39:26 +01:00
Devin Buhl 39322cbbca Revert.. 2017-03-04 12:01:59 -05:00
Leonardo Galli 46daa11c46 Fixed "wrong" quality being detected. Scan will be slower though. 2017-03-04 17:50:02 +01:00
Leonardo Galli 98e2bd00ab Fix for wrong qualities showing up. Will be slower to load though. 2017-03-04 17:48:26 +01:00
Devin Buhl 0f2234bcdc Patch/onedr0p 3 4 2017 (#1006)
* Fix link in History tab (#734)

* Fix iCal feed (#746)

* Removed DKSubs from hardcoded subs

* Fix searching all cut off unmet
2017-03-04 11:40:38 -05:00
geogolem 3f05ef810e Merge pull request #994 from geogolem/respectPageSizeWithoutReloading
respect the pageSize when initializing the layout
2017-03-04 11:36:38 -05:00
Devin Buhl aab425ee5b Patch/onedr0p updates (#998)
* few small things

* update var names

* Validate Root Folder, Minimum Avability and ProfileId on List import.
2017-03-03 21:32:52 -05:00
geogolem f7bc889723 Merge pull request #996 from geogolem/ImportExclusionsFix
the movie was not being printed correctly, and i believe this
2017-03-03 12:55:00 -05:00
geogolem cc4fb5a40b the movie was not being printed correctly, and i believe this
was also causing movies to be added when they shouldnt have been...
2017-03-03 12:52:50 -05:00
geogolem bbb4880ba4 respect the page when initializing the layout 2017-03-03 11:46:40 -05:00
geogolem a2098a5797 Merge pull request #993 from geogolem/develop
clean up the fetching on loading of MovieEditor and MovieIndex once a…
2017-03-03 09:30:42 -05:00
geogolem 93bdac31ea clean up the fetching on loading of MovieEditor and MovieIndex once and for all 2017-03-03 09:27:54 -05:00
geogolem 92a588751a Merge pull request #992 from geogolem/anotherMovieEditorFix
i dont know why i was doing this inside the for loop... It did not sc…
2017-03-03 07:38:13 -05:00
geogolem 272db9d483 i dont know why i was doing this inside the for loop... It did not scale well !
fixed
2017-03-03 07:36:37 -05:00
geogolem 9d75fc18a1 Merge pull request #990 from geogolem/fixMovieIndexOnEmpty
use clone so that we only detect empty collection
2017-03-03 04:53:56 -05:00
geogolem d8d60c6bb0 use clone so that we only detect empty collection
when collectio is empty.. not when current filter is empty but collectionis not
2017-03-03 04:50:52 -05:00
geogolem 8c656626d6 Merge pull request #988 from Radarr/filterFixMainIndex
i believe these are old code that is not needed since pagination..
2017-03-03 01:42:46 -05:00
geogolem b773119193 i believe these are old code that is not needed since pagination..
the recent change to include filterType hits this when model is undefined..
commenting out these lines fixes the problem
2017-03-03 01:40:29 -05:00
Devin Buhl bdc0db3357 Default Wanted and Cutoff to be 50 movies per page, added filtering options to Cutoff and a Search all (#984) 2017-03-02 17:28:29 -05:00
geogolem f3b3c9ff6a Merge pull request #980 from geogolem/manualImportPagingFilterFix
[Fix] filtering on Manual Import
2017-03-02 16:20:46 -05:00
geogolem 7d394dcff2 empty string case should not be only for the contains case 2017-03-02 15:21:39 -05:00
geogolem ff11388009 needed to pass the filterType, received the filterType and handle
the filterType
2017-03-02 15:14:43 -05:00
geogolem b492fece6c Merge pull request #975 from geogolem/MovieEditorFilterSave
reset filters on save..
2017-03-02 02:00:15 -05:00
geogolem 5394f1dee9 reset filters on save.. 2017-03-02 01:57:51 -05:00
geogolem e742371d15 Merge pull request #974 from geogolem/MovieEditorFixes
revert some changes -- use FullCollection (maybe just for now)
2017-03-01 23:23:09 -05:00
geogolem d03ee006fc Merge branch 'develop' into MovieEditorFixes 2017-03-01 23:15:00 -05:00
geogolem 897d76c4a2 revert some changes -- use FullCollection (maybe just for now) 2017-03-01 22:59:00 -05:00
Devin Buhl 349dd12161 Possible fix for Custom script (#973) 2017-03-01 21:22:36 -05:00
Devin Buhl d84e6c84f5 Hotfix when importing movie (#971) 2017-03-01 20:20:42 -05:00
Marcelo Castagna dfcdf8871c Fixed infinite loop. Added default destination test when adding client (#968)
removed empty spaces. changed dcaex => ex

Changed error message

changed error message

Wrong message, ups

Another message
2017-03-01 18:45:52 -05:00
Devin Buhl 7122962dc8 Date added in Movie List & Possible Fix for Importing Movies. (#969)
* Is there a need to lazyload?

* Update dates in movie list

* additional check for moviefile lazy load

* lazyload not needed...
2017-03-01 18:45:12 -05:00
Tim Turner 6432928b7d Ensure collection is synced before opening movieDetails 2017-03-01 17:30:48 -05:00
geogolem ed1d6e59b5 Merge pull request #963 from geogolem/importExclusionFix
just show imdbid or tmdbid for now in exclusions
2017-03-01 10:21:56 -05:00
geogolem 8b2d85aee5 just show imdbid or tmdbid for now in exclusions 2017-03-01 10:19:44 -05:00
geogolem 392d63fe57 MovieIndexPage Stability + MovieEditor fix (#925)
* this fixes some issues where the table
was rendering with incorrect data
prior to it being updated....

it also has the FullCollection fetched when necessary..

this will make the movie Index be accurate even after deleting files
or when returning from the movieEditor..

The footer has been improved and since
FullMovieCollection is now kept up to date the footer changes
as the user performs operations and it shows all the time with proper info
even after deletions or changes made in the movieEditor.

Prior to this it was necessary to totally refresh the page..

switching between movie editor and movie index would give unpredicatable results.

these issues have been fixed

* this is a much better solution...

still testing, but likel ready to be merged

* removing comments

* fix the movieEditor -- client side paging in movieEditor

* major code cleanup, and a slightly better implementation
no need to use FullMovieCollection..

just use moviesCollection.fullCollection when in client mode

* display a message when saving is done

perhaps eventually we can have a spinning status indicator on the
save button.. but that is not necessary right away

* some minor adjustments

* remove parseInt for tmdbId

* fix bugs

* remove some alerts

* accidentally changes this on last commit

* use the same FullMovieList everywhere

* add back alert when save is done
2017-02-28 19:46:00 -05:00
Devin Buhl 1c086b057a Patch/galileo fixes (#951)
* Update to ParseMovieTitle

* update default server to gmail to relfect other changes
2017-02-28 19:30:23 -05:00
Devin Buhl 6bbe55a46c Patch/updates onedr0p (#946)
* Update inCinemas column to abide by the short date set in settings (#511)

* Set default port to 587 for Email Settings, should help with all the people with gmail

* set SSL to true by default
2017-02-28 17:58:21 -05:00
Devin Buhl 7a269efcbc Fixed problem with TMDb list when Year is null, Revert using UrlPathEncode on newznab requests (#937)
* Fixed problem with TMDb list when Year is null

* Fuck it, just skip movies with no year. Once they have a year they will be automagically added if sync is enabled.

* Revert using UrlPathEncode on newznab requests
2017-02-27 20:16:54 -05:00
Ross Valler 06bd6db601 Expose more information to the Webhook notification (#935)
* Fix/implement Webhook notifications

* Expose more information (specifically TMDB ID)
2017-02-27 17:21:44 -05:00
Ross Valler 3dc9d3a420 Fix/implement Webhook notifications (#901) 2017-02-26 08:06:20 -05:00
geogolem 91ba503700 added more filters to the movie editor (#905) 2017-02-26 13:35:22 +01:00
Devin Buhl 28d27dca5c Add remux 1080p and 2160p as qualities (#900)
* Add Remux 1080p and 2160p as qualities, includes Tests & migration

* Whoops forgot to take this out
2017-02-26 01:14:52 -05:00
Devin Buhl e33265b58d Update parsing french movies (#899)
* Add VO, VFF and VFQ to french language

* Added VO, VFF, TRUEFRENCH and VFQ to french parser

* Update tests for french
2017-02-25 23:45:44 -05:00
Mitchell Cash 22fcb04773 NZBGet delete:scan treated as failure (#898) 2017-02-25 23:22:25 -05:00
Devin Buhl f9f67873ad small changes 2017-02-25 23:16:18 -05:00
Devin Buhl b1d345f165 Hotfix 2017-02-25 16:47:18 -05:00
geogolem 1c6a32b684 List sync with removal (#656) 2017-02-25 16:38:52 -05:00
geogolem 55ac2dd1bb fix the footer to show correct information and refresh when FullCollection changes (#893) 2017-02-25 16:37:46 -05:00
Tim Turner 997dce288d Increase fullCollection page size, update Refresh Library command 2017-02-25 13:59:30 -05:00
Devin Buhl 4d745d3600 Patch/updates (#887)
* Update HDBits internal logic

* TMDb List validation

* Add Trakt validation, update rest to implement IProviderConfig

* Update wording
2017-02-25 11:34:07 -05:00
hotio dbd1080f5c Fix poster placeholder height on small screens (#883) 2017-02-25 10:58:50 -05:00
Leonardo Galli 76963d8109 Merged branch develop into develop 2017-02-25 14:52:52 +01:00
Leonardo Galli 6b106c1b38 me = idiot 2017-02-25 14:52:46 +01:00
hotio 0016cc59af Small UI fixes (#882) 2017-02-25 08:05:40 -05:00
Leonardo Galli 8b9d0f7b19 Fixed an issue where an unloaded movie could case linking to fail. 2017-02-25 13:52:16 +01:00
Leonardo Galli 3a4b01cf6f Maybe fix issue with imported files not being linked to the movie? 2017-02-25 13:50:12 +01:00
Leonardo Galli 15acb9d204 Search is now fixed too. 2017-02-25 13:17:31 +01:00
Leonardo Galli 21fa96f78f Should fix most issues with paging. 2017-02-25 13:04:32 +01:00
Leonardo Galli fe4e11d9c1 Add first steps of paging to movie editor. 2017-02-25 12:22:36 +01:00
Leonardo Galli d22d5fcfc3 Merged branch develop into develop 2017-02-25 12:00:34 +01:00
Leonardo Galli cdca4a8585 First fixes for Movie Editor. Testing to see if this approach could work. 2017-02-25 12:00:23 +01:00
Devin Buhl 46552785f5 HDBits prefer/require internal release (#584) (#881) 2017-02-24 22:41:00 -05:00
Devin Buhl 816c62979a Ignore Deleted Movies (#755) (#879) 2017-02-24 21:30:12 -05:00
Leonardo Galli ca164c2a24 Fix missing showing downloaded instead. 2017-02-25 00:04:53 +01:00
Leonardo Galli bf3c6f95eb Fix issue where details page wont load. 2017-02-24 21:44:21 +01:00
Leonardo Galli f07f2e77f6 Paging for movies :) (#861)
* First steps.

* Not really sure what I am doing here.

* Pretty hacky, but it works :)

* First filter works now.

* Fix all filters.

* Fix some filters.

* PageSize saving now works.

* Fixed items being added when a refresh movie is done.

* Downloaded sort not working.

* Sorting by downloaded status now works.

Extremely hacky, but ¯\_(ツ)_/¯

* Fixed issue where users were stuck when filtering.

* Sorting via that button works now.

* Removed temp thingy.
2017-02-24 19:52:40 +01:00
Devin Buhl 50fdbd896c Bug fixes (#874)
* Update Torrent and Usenet DownloadStation

* Update Download Tests

* Fix TorrentPotato not finding results #754

* Update UpdateMediaInfoService and Tests #572

* Ignore plex otimized versions w/ tests #391

* Remove Xem Serivce files and tests #386

* Ignore TV Episode from IMDb lists
2017-02-24 09:40:25 -05:00
geogolem bab7bd20cd the Search All Missing button (#860)
was searching all missing and monitored only
though the dialog that popped up was informing the user it was
going to search for x movies, where x corresponded to the number
of movies filtered on the page.

I changed this button, so now it will search all the items as they are
filtered on the page.

For example, if you want to search all missing (regardless of
monitor/unmonitor) click the all filter and click the button.

If you want to search only monitored/missing, click the monitored button
and then click the search all button...

this included the old functionality, but allows the user alot more
flexibility...

i also added the all filter, and refactored the code,
so that builds the expression for the LINQ.. since this needed to be
used in two places.. just implement it once and use it in both places..

I tested this out... and stepped through with debugger.. i also did a
quick test of everything else. Im confident that the featureset
implemented and bugs fixed by this commit are OK... Im not 100% that
other parts of radarr dont use the same MissingMovieSearch routines..
but i dont think so...
2017-02-24 03:22:55 -05:00
geogolem 0678908fd9 Cleanup min availability (#846)
* some minor cleanup + changed filter on wanted/missing

* MovieIndex Footer add counts + update legend

* minor spelling error + typo
2017-02-23 07:08:30 -05:00
geogolem 9d29776e8e some minor cleanup + changed filter on wanted/missing (#845) 2017-02-23 06:33:54 -05:00
geogolem 140a220340 Min availability (#816)
* availability specification to prevent downloading titles before their
release

* pull inCinamas status out of js handlebars and set it in SkyHook

* minor code improvement

* add incinemas to footer

* typo

* another typo

* release date handling

* still print cinema date out for announced titles

* revert a minor change from before since its unnecessary

* early implementation of minimumAvailability --> when does radarr
consider a movie "available" should be specified by user
default to "Physical release?"

this isn't functional yet, but it has a skeleton + comments. I dont
know how to have the minimumavailability attribute default to something
or to have it actually populate the Movieinfo object
could use some help with that

* adding another comment for another location that might need to be
updated to handle minimumAvailability

* the implementation is now function;
however, i still need to specify default values for minimumAvailability

* missed these changes in the previous commit

* fix rounded corners on new field in editmovie dialog

* add minimum availability specification to the addMovie page

* minor adjustment from last commit

* handle the case where minimumavailability has never yet been set
nullstring.. if its never been set, default to Released (Physical/Web)
represented by integer value  3

* minAvailability specification on NetImport lists

* add support for min availability to the movie editor

* use enum MovieStatusType values directly

makes for cleaner code

* need to fix up the migration forgot in last commit

* cleaning up code, proper case

* erroneous code added in this feature needed to be removed

* update "Wanted" page to take into account minimumAvailability

* implement preDB minimumAvailability as default.. behaves same as
Physical/Web a few comments with TODO for when preDB is implemented

* minor adjustment

* remove some unused code (leave commented for now)

* improve code for minimumavailability and add option for
availabilitydelay (but doesnt do anything yet)

* improve isAvailable method

* clean up and fix helper info on indexer configuration page

* add buttons in Wanted/Missing view
2017-02-23 00:03:48 -05:00
Devin Buhl 731e607666 Add NZB Station for Synology (#841) 2017-02-22 18:42:11 -05:00
Devin Buhl 97ee66465d Patch/filter trakt (#838)
* Update wording for Certification

* Add Filter Options for Trakt
2017-02-22 16:12:42 -05:00
Leonardo Galli a0050fedd3 Fixed language parsing of movies with language in movie name.
Fixes #793
2017-02-22 13:40:06 +01:00
Tim Turner 89e5001bad Patch/bulk import tests (#833)
* Ensure duplicates are not imported

* Clean up some unnecessary code

* Added some fancy tests for bulk import.
2017-02-22 11:21:05 +01:00
Tim Turner 056fb154a8 Patch/bulk import qol (#785)
* Filter out existing movies upon import
* Update collection based on what is imported
* Ensure root folders are loaded before collectionview

TODO:
* Ensure grid region exists
* Return information about what wasn't imported
* Filter collection based on duplicates
2017-02-21 15:31:31 -05:00
Leonardo Galli 3edc2b80cf Update .travis.yml 2017-02-20 22:27:07 +01:00
Mihai Blaga a1745cd02e URL Encode for newznab query strings, closes #818 (#819)
Closes #818
2017-02-20 11:17:28 -05:00
Mitchell Cash 1c0f9b64ca Rename Sonarr to Radarr in DownloadStation client (#812) 2017-02-20 11:15:53 -05:00
hotio 7d6a518f30 Update notification logos (#804) 2017-02-20 11:15:26 -05:00
Ryan Matthews 9f2fcebc24 Fixes error message for MovieExistsValidator to state the movie doesn't exist (#723) (#808)
Fixes #723
2017-02-19 19:05:37 +01:00
Ryan Matthews 91295f50b0 Set PROWL application to Radarr (#770) (#807) 2017-02-19 18:55:01 +01:00
Devin Buhl 68bf97f52c TMDb Lists should be working now :) (#775)
* Remove un-used imports

* Some small update to Net Import
2017-02-16 14:40:04 -05:00
Devin Buhl 363048e68e Roll back some code on Net Import (#772) 2017-02-16 13:29:21 -05:00
Marcelo Castagna 9c20c0b889 Check to see if output path is right when DownloadClient.Test is invoked (#768)
changed to string interpolation a few lines
2017-02-16 08:22:02 -05:00
Devin Buhl 50891e5dd7 TMDb Filtering Options: Rating, Min Votes, Min Vote Ave, Original Language, TMDb Genre Ids (CSV), (#765) 2017-02-16 04:46:35 -05:00
Devin Buhl f393a95501 Small consistancy updates to PTP and AwesomeHD (#758) 2017-02-15 18:02:24 -05:00
Devin Buhl a68dd6d2f7 Patch/onedr0p (#757)
* Fix double ? in log

* Rename Download Station to TorrentDownloadStation

* Rename Trakt, TMDb, and set Enable sync default to false

* Added omgwtfnzbs Newznab prefix

* Update Media info for Windows/macOS to 0.7.92.1
2017-02-15 17:37:23 -05:00
Marcelo Castagna dadf6708ab Handle download data diskstation (#744)
* Delete data manually after finished seeding

* Removed try/catch
2017-02-14 09:29:47 -05:00
Tim Turner 7a86c78896 Merged patch/update-files-tab-onChange into develop 2017-02-13 20:08:31 -05:00
Tim Turner 81688399c0 When refreshing movie, refresh Files tab 2017-02-13 20:08:07 -05:00
Devin Buhl 1e28a2e5d4 Feature/Add TMDb Functionality (#739)
* Inital TMDb List, needs paging support and user lists, private or public

* Clean up Base

* TMDb grabs upto 5 pages for import, update validation, added minimum vote average

* Added logic for MovieLinksTemplate

* Clean up a bit

* Add Public Lists
2017-02-13 09:11:20 -05:00
zductiv c5bb259555 add downloaded quality column to movie editor (#738) 2017-02-13 08:25:37 -05:00
Devin Buhl 0d5d75d6ea Update ISSUE_TEMPLATE.md 2017-02-13 08:14:37 -05:00
Devin Buhl 5bae9bbbcc Update PULL_REQUEST_TEMPLATE.md 2017-02-13 08:08:37 -05:00
Devin Buhl a3e681078f Clean up Trakt a little (#735) 2017-02-13 00:38:19 -05:00
Devin Buhl 758228e159 Update dl-clients (#732) 2017-02-12 20:07:57 -05:00
Devin Buhl 1b900a006f Add Synology Download Station (#725)
* Add Synology Download Station

* Update DownloadStation Client from Sonarr
2017-02-12 19:44:47 -05:00
Leonardo Galli 9b5c5169ef fix pending release table. 2017-02-12 16:33:36 +01:00
Devin Buhl e78a55ac6e Fix Hardcoded .DKSubs. (#726)
* Fix Hardcoded .DKSubs.

* Added tests
2017-02-12 09:19:48 -05:00
Devin Buhl e82cf70399 NetImport - Do not allow TV Series / Mini-Series (works with IMDb) #699 (#727)
* Do not allow TV Series / Mini-Series (works with IMDb) #699

* Ignore Case
2017-02-12 09:16:15 -05:00
Devin Buhl e7d65ee4ae Patch/re add ghost migrations (#724)
* Remove Wombles and Kickass Data from tables

* And that's why you build
2017-02-12 07:08:39 -05:00
Devin Buhl 1db3669afa Patch/onedr0p (#716)
* Alter IMDb lists to what was requested: #697

* Update Pending Release

* Tests (these need to be updated further)

* Alter table migration, movieId already exists

* Update HouseKeeping for pending release

* Fix migratiom and pendingrelease housekeeping
2017-02-12 06:57:07 -05:00
Mitchell Cash 93e55b7575 Increase timeout when waiting for rTorrent to finish adding torrent (#721)
Increase timeout when waiting for rTorrent to finish adding torrent
2017-02-12 03:07:38 -05:00
Devin Buhl f850c65b56 Update ISSUE_TEMPLATE.md 2017-02-11 18:29:56 -05:00
Leonardo Galli 297348fffe Merged branch develop into develop 2017-02-11 23:25:35 +01:00
Leonardo Galli 07ff6558d1 Hopefully fixes a lot of null reference bugs in BulkImport 2017-02-11 23:25:27 +01:00
Leonardo Galli 85843efcb0 Added test for ! 2017-02-11 23:24:10 +01:00
zductiv 3d4b1c3be5 changed sort options to match UI (#707) 2017-02-11 15:04:19 -05:00
Tim Turner a3f389af5e Fix RescanMovie command for single movie
Partial fix for #669
2017-02-11 14:38:17 -05:00
Leonardo Galli 1aeb3c6fd6 Should fix blacklist items disappearing.
Fixes #653
2017-02-11 18:32:54 +01:00
Leonardo Galli 6ab6c016c0 Fix manual import for when downloaded movies are in a folder 2017-02-11 18:26:35 +01:00
Leonardo Galli a1961603d7 Merged branch develop into develop 2017-02-11 17:40:31 +01:00
Leonardo Galli 50ac95dec5 Update parser tests. 2017-02-11 17:40:01 +01:00
Leonardo Galli a16e46cf38 Fix parsing with lower bluray qualities.
Fixes #706
2017-02-11 16:59:15 +01:00
zductiv ea33b75764 search all missing movie works - missing tab only (#710) 2017-02-11 08:48:02 -05:00
Devin Buhl 31e657d052 Limit TMDb requests when importing via IMDBid (#703) 2017-02-10 23:03:24 -05:00
Leonardo Galli fe0dfef83c Fixes issue with movies with same name but different years being downloaded. 2017-02-10 19:40:10 +01:00
Leonardo Galli bc1a47ff5a Fixed a few parser issues. Also added some tests.
Fixes #549
2017-02-10 19:00:16 +01:00
Leonardo Galli 4e8089dd42 Cutoff tab actually working now. 2017-02-10 17:30:35 +01:00
geogolem 6dc9f90a8b fix trakt links for movies (like sonarr for shows) (#690) 2017-02-10 04:07:38 -05:00
Devin Buhl 1aae3ae2b5 Fixed Sorting In Wanted and Cutoff (#693) 2017-02-10 04:04:50 -05:00
Devin Buhl 75436bcce4 Pass at seeing if this works on linux now (#692) 2017-02-10 03:17:28 -05:00
zductiv 61df3ef40e Small UI changes (#691)
* Update FileManagementViewTemplate.hbs

* Update PermissionsViewTemplate.hbs

* modified UI, removed old episode references
2017-02-10 03:14:51 -05:00
Devin Buhl f45aab27d1 Add required flag for PTP (#688) 2017-02-10 01:35:47 -05:00
Devin Buhl f477c46406 Wanted & Missing (#687)
* Remove Season Pass, Update Header name, remove useless function

* Cutoff Tab now works
2017-02-10 00:15:41 -05:00
Devin Buhl 2af07d7e0d * Make Missing/Wanted Work again (#686)
* Make Missing/Wanted Load
2017-02-09 22:52:13 -05:00
Devin Buhl df691488a9 Fixed MovieMissingModule failed while processing [MovieDownloadedEvent] 2017-02-09 16:52:35 -05:00
Tim Turner 800e7ae508 UI Enhancements for Manual Import (#681)
Fixes #589
Fixes #632
2017-02-09 16:25:51 -05:00
Leonardo Galli fcf156293e May be fix loading view? Idk. 2017-02-09 16:48:14 +01:00
Tim Turner 94f44a0eb7 Display loading view when changing page size 2017-02-08 17:00:37 -05:00
Leonardo Galli 1e2c28f67a Merged branch develop into develop 2017-02-08 22:14:04 +01:00
Leonardo Galli 62b45f7ea7 Fix paging breaking in bulk import. 2017-02-08 22:11:11 +01:00
Devin Buhl f577590ad6 Fix ordering in PTP, should prefer GP releases (#667)
* Fix ordering in PTP, should prefer GP releases

* Apply more checks
2017-02-08 14:26:11 -05:00
Devin Buhl 0941247f63 Patch/onedr0p updates (#664)
* Remove button for require GP in PassThePopcorn

* Fix AwesomeHD when search results yeild 1 torrent result

* Add try/catch block
2017-02-08 10:36:07 -05:00
schumi2004 3170060f37 Make Movie Title and Status sortable on Wanted tab (#662) 2017-02-08 09:22:49 -05:00
Leonardo Galli 35b384439f Bulk Import. (#583)
* First pass at bulk import.

* First pass at paging implementation for bulk import.

* Another pass at UI for bulk import

WHY WON'T THE ROWS SELECT?!?!

* Paging mostly done. UI needs to show loading still.

* Fix for selection

* fixes.

* Add caching to bulk import

* Tried to fix paging.

* Fix has next

* Fix link error.

* Pageable now works almost perfectly.

Collection now works really nicely when paged. Also movies from different pages can be added no problemo.

* /bulk-import: ProfileCell

Various other QoL changes

* Profile selection works now

Still kinda hacky

* Default monitored to true.

* Add Monitor Cell

Update styling, added path tooltip as well

* Update model when changing tmdbId

Ensure monitor status doesn't change as well

* Added spinner feedback for tmdbid cell.

* /bulk-import: Add page-size selector
2017-02-07 19:09:36 -05:00
Devin Buhl 0d1150d4d2 Wait 5 seconds before getting the next 35 movies from TMDb using X-RateLimit-Remaining (#647)
* Wait 5 seconds before getting the next 35 movies from TMDb using X-RateLimit-Remaining

* Update SkyHookProxy.cs
2017-02-07 16:55:57 -05:00
Devin Buhl 1f68b46575 Correct the Kickass migration (#649) 2017-02-07 16:36:35 -05:00
Devin Buhl 32c5c4d741 Update notif list warning when importing from a list (#648) 2017-02-07 16:29:12 -05:00
Devin Buhl 0cb15121e5 Fix movies not showing up in Queue when downloading (#640) 2017-02-07 03:03:15 -05:00
Devin Buhl 808033a01c Fixed Movie link in history tab (#637) 2017-02-06 20:57:53 -05:00
Devin Buhl 736e0d2e70 Clean up download clients to use radarr as label, fix hoduken, and blackhole. (#635)
* change default label to radarr for rtorrent

* change utorrent default label to radarr

* change hadoken to use radarr label

* change blackhole default to radarr, update hoduken

* Episodes don't work, but it needs to be here

* Fix blackhole xD
2017-02-06 19:51:30 -05:00
Devin Buhl 96741570c5 Use Movie Name-TmdbId for slug, update toUrlSlug (#629) 2017-02-06 17:01:02 -05:00
Devin Buhl 8feb3fee98 Removed Wombles and Kickass, updated torrentpotato and torznab (#625)
* Remove Wombles and kickass

* Clean up TorrentPotato Request Gen

* Opps, we need to use SupportedMovieSearchParameters xD

* Consistancy

* Clean up Newznab Request Gen, add year to search if cannot use IMDB

* Added SpecialCharRegex to remove \ / : &
2017-02-06 16:34:11 -05:00
Abzie 07e3e44a68 Various ui text fixes (#620)
* Fix notification text on rename

* Fixed profile tooltip for minimum sizes

* Updated link to now point to Radarr's FAQ

* Revert "Updated link to now point to Radarr's FAQ"

This reverts commit 726e0cc870.

Revert 726e0cc

* Fixed: Updated link to now point to Radarr's FAQ
2017-02-06 13:58:24 -05:00
vertigo235 d67d405024 Delay Profile: Fix for when preferred words is null. (#618) 2017-02-06 14:07:17 +01:00
358 changed files with 9258 additions and 3939 deletions
+8 -5
View File
@@ -1,9 +1,12 @@
**Description:**
Provide a description of the feature request or bug here, the more details the better.
Please also try to include the following if you are reporting a bug
**Radarr Version:**
**Logs:**
Please use the search bar and make sure you are not submitting an already submitted issue. Please use the search bar and make sure you are not submitting an already submitted issue.
Visit our [Discord server](https://discord.gg/NWYch8M) for support or longer discussions.
Provide a description of the feature request or bug, the more details the better.
When possible include a log!
Please use our [Discord server](https://discord.gg/NWYch8M) for support or longer discussions.
+2 -4
View File
@@ -2,13 +2,11 @@
YES | NO YES | NO
#### Description #### Description
A few sentences describing the overall goals of the pull request's commits.
#### Todos #### Todos
- [ ] Tests - [ ] Tests
- [ ] Documentation
#### Issues Fixed or Closed by this PR #### Issues Fixed or Closed by this PR
* * #
+1 -1
View File
@@ -4,7 +4,7 @@ addons:
apt: apt:
packages: packages:
- nodejs - nodejs
- npm # - npm apparently not needed anymore.
script: script:
- ./build.sh - ./build.sh
- chmod +x test.sh - chmod +x test.sh
+1
View File
@@ -40,6 +40,7 @@
"run-sequence": "1.1.1", "run-sequence": "1.1.1",
"streamqueue": "1.1.0", "streamqueue": "1.1.0",
"tar.gz": "0.1.1", "tar.gz": "0.1.1",
"url-search-params": "^0.6.1",
"webpack": "1.12.0", "webpack": "1.12.0",
"webpack-stream": "2.1.0" "webpack-stream": "2.1.0"
} }
Binary file not shown.
Binary file not shown.
+43 -30
View File
@@ -16,13 +16,13 @@ namespace NzbDrone.Api.Calendar
{ {
public class CalendarFeedModule : NzbDroneFeedModule public class CalendarFeedModule : NzbDroneFeedModule
{ {
private readonly IEpisodeService _episodeService; private readonly IMovieService _movieService;
private readonly ITagService _tagService; private readonly ITagService _tagService;
public CalendarFeedModule(IEpisodeService episodeService, ITagService tagService) public CalendarFeedModule(IMovieService movieService, ITagService tagService)
: base("calendar") : base("calendar")
{ {
_episodeService = episodeService; _movieService = movieService;
_tagService = tagService; _tagService = tagService;
Get["/NzbDrone.ics"] = options => GetCalendarFeed(); Get["/NzbDrone.ics"] = options => GetCalendarFeed();
@@ -37,7 +37,7 @@ namespace NzbDrone.Api.Calendar
var start = DateTime.Today.AddDays(-pastDays); var start = DateTime.Today.AddDays(-pastDays);
var end = DateTime.Today.AddDays(futureDays); var end = DateTime.Today.AddDays(futureDays);
var unmonitored = false; var unmonitored = false;
var premiersOnly = false; //var premiersOnly = false;
var tags = new List<int>(); var tags = new List<int>();
// TODO: Remove start/end parameters in v3, they don't work well for iCal // TODO: Remove start/end parameters in v3, they don't work well for iCal
@@ -46,7 +46,7 @@ namespace NzbDrone.Api.Calendar
var queryPastDays = Request.Query.PastDays; var queryPastDays = Request.Query.PastDays;
var queryFutureDays = Request.Query.FutureDays; var queryFutureDays = Request.Query.FutureDays;
var queryUnmonitored = Request.Query.Unmonitored; var queryUnmonitored = Request.Query.Unmonitored;
var queryPremiersOnly = Request.Query.PremiersOnly; // var queryPremiersOnly = Request.Query.PremiersOnly;
var queryTags = Request.Query.Tags; var queryTags = Request.Query.Tags;
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value); if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
@@ -69,10 +69,10 @@ namespace NzbDrone.Api.Calendar
unmonitored = bool.Parse(queryUnmonitored.Value); unmonitored = bool.Parse(queryUnmonitored.Value);
} }
if (queryPremiersOnly.HasValue) //if (queryPremiersOnly.HasValue)
{ //{
premiersOnly = bool.Parse(queryPremiersOnly.Value); // premiersOnly = bool.Parse(queryPremiersOnly.Value);
} //}
if (queryTags.HasValue) if (queryTags.HasValue)
{ {
@@ -80,43 +80,56 @@ namespace NzbDrone.Api.Calendar
tags.AddRange(tagInput.Split(',').Select(_tagService.GetTag).Select(t => t.Id)); tags.AddRange(tagInput.Split(',').Select(_tagService.GetTag).Select(t => t.Id));
} }
var episodes = _episodeService.EpisodesBetweenDates(start, end, unmonitored); var movies = _movieService.GetMoviesBetweenDates(start, end, unmonitored);
var calendar = new Ical.Net.Calendar var calendar = new Ical.Net.Calendar
{ {
ProductId = "-//sonarr.tv//Sonarr//EN" ProductId = "-//radarr.video//Radarr//EN"
}; };
foreach (var movie in movies.OrderBy(v => v.Added))
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))
{ {
if (premiersOnly && (episode.SeasonNumber == 0 || episode.EpisodeNumber != 1)) if (tags.Any() && tags.None(movie.Tags.Contains))
{
continue;
}
if (tags.Any() && tags.None(episode.Series.Tags.Contains))
{ {
continue; continue;
} }
var occurrence = calendar.Create<Event>(); var occurrence = calendar.Create<Event>();
occurrence.Uid = "NzbDrone_episode_" + episode.Id; occurrence.Uid = "NzbDrone_movie_" + movie.Id;
occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative; occurrence.Status = movie.HasFile ? EventStatus.Confirmed : EventStatus.Tentative;
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value) { HasTime = true };
occurrence.End = new CalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime)) { HasTime = true };
occurrence.Description = episode.Overview;
occurrence.Categories = new List<string>() { episode.Series.Network };
switch (episode.Series.SeriesType) switch (movie.Status)
{ {
case SeriesTypes.Daily: case MovieStatusType.PreDB:
occurrence.Summary = $"{episode.Series.Title} - {episode.Title}"; if (movie.PhysicalRelease != null)
{
occurrence.Start = new CalDateTime(movie.PhysicalRelease.Value) { HasTime = true };
occurrence.End = new CalDateTime(movie.PhysicalRelease.Value.AddMinutes(movie.Runtime)) { HasTime = true };
}
break; break;
case MovieStatusType.InCinemas:
if (movie.InCinemas != null)
{
occurrence.Start = new CalDateTime(movie.InCinemas.Value) { HasTime = true };
occurrence.End = new CalDateTime(movie.InCinemas.Value.AddMinutes(movie.Runtime)) { HasTime = true };
}
break;
case MovieStatusType.Announced:
continue; // no date
default: default:
occurrence.Summary =$"{episode.Series.Title} - {episode.SeasonNumber}x{episode.EpisodeNumber:00} - {episode.Title}"; if (movie.PhysicalRelease != null)
{
occurrence.Start = new CalDateTime(movie.PhysicalRelease.Value) { HasTime = true };
occurrence.End = new CalDateTime(movie.PhysicalRelease.Value.AddMinutes(movie.Runtime)) { HasTime = true };
}
break; break;
} }
occurrence.Description = movie.Overview;
occurrence.Categories = new List<string>() { movie.Studio };
occurrence.Summary = $"{movie.Title}";
} }
var serializer = (IStringSerializer) new SerializerFactory().Build(calendar.GetType(), new SerializationContext()); var serializer = (IStringSerializer) new SerializerFactory().Build(calendar.GetType(), new SerializationContext());
@@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Nancy;
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Api.Movie; using NzbDrone.Api.Movie;
using NzbDrone.Api.Series;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
@@ -8,6 +8,7 @@ namespace NzbDrone.Api.Config
public int MinimumAge { get; set; } public int MinimumAge { get; set; }
public int Retention { get; set; } public int Retention { get; set; }
public int RssSyncInterval { get; set; } public int RssSyncInterval { get; set; }
public int AvailabilityDelay { get; set; }
} }
public static class IndexerConfigResourceMapper public static class IndexerConfigResourceMapper
@@ -19,6 +20,7 @@ namespace NzbDrone.Api.Config
MinimumAge = model.MinimumAge, MinimumAge = model.MinimumAge,
Retention = model.Retention, Retention = model.Retention,
RssSyncInterval = model.RssSyncInterval, RssSyncInterval = model.RssSyncInterval,
AvailabilityDelay = model.AvailabilityDelay,
}; };
} }
} }
@@ -6,6 +6,11 @@ namespace NzbDrone.Api.Config
public class NetImportConfigResource : RestResource public class NetImportConfigResource : RestResource
{ {
public int NetImportSyncInterval { get; set; } public int NetImportSyncInterval { get; set; }
public string ListSyncLevel { get; set; }
public string ImportExclusions { get; set; }
public string TraktAuthToken { get; set; }
public string TraktRefreshToken { get; set; }
public int TraktTokenExpiry { get; set; }
} }
public static class NetImportConfigResourceMapper public static class NetImportConfigResourceMapper
@@ -14,7 +19,12 @@ namespace NzbDrone.Api.Config
{ {
return new NetImportConfigResource return new NetImportConfigResource
{ {
NetImportSyncInterval = model.NetImportSyncInterval NetImportSyncInterval = model.NetImportSyncInterval,
ListSyncLevel = model.ListSyncLevel,
ImportExclusions = model.ImportExclusions,
TraktAuthToken = model.TraktAuthToken,
TraktRefreshToken = model.TraktRefreshToken,
TraktTokenExpiry = model.TraktTokenExpiry,
}; };
} }
} }
@@ -71,7 +71,7 @@ namespace NzbDrone.Api.EpisodeFiles
private void DeleteEpisodeFile(int id) private void DeleteEpisodeFile(int id)
{ {
var episodeFile = _mediaFileService.Get(id); var episodeFile = _mediaFileService.Get(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId); var series = _seriesService.GetSeries(episodeFile.SeriesId);
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath); var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
@@ -66,13 +66,9 @@ namespace NzbDrone.Api.Extensions.Pipelines
private Response LogError(NancyContext context, Exception exception) private Response LogError(NancyContext context, Exception exception)
{ {
var response = _errorPipeline.HandleException(context, exception); var response = _errorPipeline.HandleException(context, exception);
context.Response = response; context.Response = response;
LogEnd(context); LogEnd(context);
context.Response = null; context.Response = null;
return response; return response;
} }
@@ -80,12 +76,9 @@ namespace NzbDrone.Api.Extensions.Pipelines
{ {
if (request.Url.Query.IsNotNullOrWhiteSpace()) if (request.Url.Query.IsNotNullOrWhiteSpace())
{ {
return string.Concat(request.Url.Path, "?", request.Url.Query); return string.Concat(request.Url.Path, request.Url.Query);
}
else
{
return request.Url.Path;
} }
return request.Url.Path;
} }
} }
} }
@@ -0,0 +1,175 @@
using System.Collections;
using System.Collections.Generic;
using Nancy;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Parser;
using System.Linq;
using System;
using Marr.Data;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.RootFolders;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Movie
{
public class UnmappedComparer : IComparer<UnmappedFolder>
{
public int Compare(UnmappedFolder a, UnmappedFolder b)
{
return a.Name.CompareTo(b.Name);
}
}
public class MovieBulkImportModule : NzbDroneRestModule<MovieResource>
{
private readonly ISearchForNewMovie _searchProxy;
private readonly IRootFolderService _rootFolderService;
private readonly IMakeImportDecision _importDecisionMaker;
private readonly IDiskScanService _diskScanService;
private readonly ICached<Core.Tv.Movie> _mappedMovies;
private readonly IMovieService _movieService;
public MovieBulkImportModule(ISearchForNewMovie searchProxy, IRootFolderService rootFolderService, IMakeImportDecision importDecisionMaker,
IDiskScanService diskScanService, ICacheManager cacheManager, IMovieService movieService)
: base("/movies/bulkimport")
{
_searchProxy = searchProxy;
_rootFolderService = rootFolderService;
_importDecisionMaker = importDecisionMaker;
_diskScanService = diskScanService;
_mappedMovies = cacheManager.GetCache<Core.Tv.Movie>(GetType(), "mappedMoviesCache");
_movieService = movieService;
Get["/"] = x => Search();
}
private Response Search()
{
if (Request.Query.Id == 0)
{
//Todo error handling
}
RootFolder rootFolder = _rootFolderService.Get(Request.Query.Id);
int page = Request.Query.page;
int per_page = Request.Query.per_page;
int min = (page - 1) * per_page;
int max = page * per_page;
var unmapped = rootFolder.UnmappedFolders.OrderBy(f => f.Name).ToList();
int total_count = unmapped.Count;
if (Request.Query.total_entries.HasValue)
{
total_count = Request.Query.total_entries;
}
max = total_count >= max ? max : total_count;
var paged = unmapped.GetRange(min, max-min);
var mapped = paged.Select(f =>
{
Core.Tv.Movie m = null;
var mappedMovie = _mappedMovies.Find(f.Name);
if (mappedMovie != null)
{
return mappedMovie;
}
var parsedTitle = Parser.ParseMoviePath(f.Name);
if (parsedTitle == null)
{
m = new Core.Tv.Movie
{
Title = f.Name.Replace(".", " ").Replace("-", " "),
Path = f.Path,
};
}
else
{
m = new Core.Tv.Movie
{
Title = parsedTitle.MovieTitle,
Year = parsedTitle.Year,
ImdbId = parsedTitle.ImdbId,
Path = f.Path
};
}
var files = _diskScanService.GetVideoFiles(f.Path);
var decisions = _importDecisionMaker.GetImportDecisions(files.ToList(), m, true);
var decision = decisions.Where(d => d.Approved && !d.Rejections.Any()).FirstOrDefault();
if (decision != null)
{
var local = decision.LocalMovie;
m.MovieFile = new LazyLoaded<MovieFile>(new MovieFile
{
Path = local.Path,
Edition = local.ParsedMovieInfo.Edition,
Quality = local.Quality,
MediaInfo = local.MediaInfo,
ReleaseGroup = local.ParsedMovieInfo.ReleaseGroup,
RelativePath = f.Path.GetRelativePath(local.Path)
});
}
mappedMovie = _searchProxy.MapMovieToTmdbMovie(m);
if (mappedMovie != null)
{
mappedMovie.Monitored = true;
_mappedMovies.Set(f.Name, mappedMovie, TimeSpan.FromDays(2));
return mappedMovie;
}
return null;
});
return new PagingResource<MovieResource>
{
Page = page,
PageSize = per_page,
SortDirection = SortDirection.Ascending,
SortKey = Request.Query.sort_by,
TotalRecords = total_count - mapped.Where(m => m == null).Count(),
Records = MapToResource(mapped.Where(m => m != null)).ToList()
}.AsResponse();
}
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
{
foreach (var currentMovie in movies)
{
var resource = currentMovie.ToResource();
var poster = currentMovie.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
{
resource.RemotePoster = poster.Url;
}
yield return resource;
}
}
}
}
+19 -36
View File
@@ -13,77 +13,60 @@ using NzbDrone.SignalR;
namespace NzbDrone.Api.EpisodeFiles namespace NzbDrone.Api.EpisodeFiles
{ {
public class MovieFileModule : NzbDroneRestModuleWithSignalR<MovieFileResource, MovieFile> public class MovieFileModule : NzbDroneRestModuleWithSignalR<MovieFileResource, MovieFile>, IHandle<MovieFileAddedEvent>
//IHandle<EpisodeFileAddedEvent>
{ {
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IRecycleBinProvider _recycleBinProvider; private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IMovieService _seriesService; private readonly IMovieService _movieService;
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
private readonly Logger _logger; private readonly Logger _logger;
public MovieFileModule(IBroadcastSignalRMessage signalRBroadcaster, public MovieFileModule(IBroadcastSignalRMessage signalRBroadcaster,
IMediaFileService mediaFileService, IMediaFileService mediaFileService,
IRecycleBinProvider recycleBinProvider, IRecycleBinProvider recycleBinProvider,
IMovieService seriesService, IMovieService movieService,
IQualityUpgradableSpecification qualityUpgradableSpecification, IQualityUpgradableSpecification qualityUpgradableSpecification,
Logger logger) Logger logger)
: base(signalRBroadcaster) : base(signalRBroadcaster)
{ {
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_recycleBinProvider = recycleBinProvider; _recycleBinProvider = recycleBinProvider;
_seriesService = seriesService; _movieService = movieService;
_qualityUpgradableSpecification = qualityUpgradableSpecification; _qualityUpgradableSpecification = qualityUpgradableSpecification;
_logger = logger; _logger = logger;
GetResourceById = GetMovieFile; GetResourceById = GetMovieFile;
/*GetResourceAll = GetEpisodeFiles;
UpdateResource = SetQuality;*/
UpdateResource = SetQuality; UpdateResource = SetQuality;
DeleteResource = DeleteEpisodeFile; DeleteResource = DeleteMovieFile;
} }
private MovieFileResource GetMovieFile(int id) private MovieFileResource GetMovieFile(int id)
{ {
var episodeFile = _mediaFileService.GetMovie(id); var movie = _mediaFileService.GetMovie(id);
return episodeFile.ToResource(); return movie.ToResource();
} }
/*private List<EpisodeFileResource> GetEpisodeFiles() private void SetQuality(MovieFileResource movieFileResource)
{ {
if (!Request.Query.SeriesId.HasValue) var movieFile = _mediaFileService.GetMovie(movieFileResource.Id);
{ movieFile.Quality = movieFileResource.Quality;
throw new BadRequestException("seriesId is missing"); _mediaFileService.Update(movieFile);
}
var seriesId = (int)Request.Query.SeriesId;
var series = _seriesService.GetSeries(seriesId);
return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification));
}
*/
private void SetQuality(MovieFileResource episodeFileResource)
{
var episodeFile = _mediaFileService.GetMovie(episodeFileResource.Id);
episodeFile.Quality = episodeFileResource.Quality;
_mediaFileService.Update(episodeFile);
} }
private void DeleteEpisodeFile(int id) private void DeleteMovieFile(int id)
{ {
var episodeFile = _mediaFileService.GetMovie(id); var movieFile = _mediaFileService.GetMovie(id);
var series = _seriesService.GetMovie(episodeFile.MovieId); var movie = _movieService.GetMovie(movieFile.MovieId);
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath); var fullPath = Path.Combine(movie.Path, movieFile.RelativePath);
_logger.Info("Deleting episode file: {0}", fullPath); _logger.Info("Deleting movie file: {0}", fullPath);
_recycleBinProvider.DeleteFile(fullPath); _recycleBinProvider.DeleteFile(fullPath);
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual); _mediaFileService.Delete(movieFile, DeleteMediaFileReason.Manual);
} }
public void Handle(EpisodeFileAddedEvent message) public void Handle(MovieFileAddedEvent message)
{ {
BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.Id); BroadcastResourceChange(ModelAction.Updated, message.MovieFile.Id);
} }
} }
} }
@@ -0,0 +1,78 @@
using NzbDrone.Api.Movie;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Movies
{
public abstract class MovieModuleWithSignalR : NzbDroneRestModuleWithSignalR<MovieResource, Core.Tv.Movie>,
IHandle<MovieGrabbedEvent>,
IHandle<MovieDownloadedEvent>
{
protected readonly IMovieService _movieService;
protected readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
protected MovieModuleWithSignalR(IMovieService movieService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(signalRBroadcaster)
{
_movieService = movieService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
GetResourceById = GetMovie;
}
protected MovieModuleWithSignalR(IMovieService movieService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster,
string resource)
: base(signalRBroadcaster, resource)
{
_movieService = movieService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
GetResourceById = GetMovie;
}
protected MovieResource GetMovie(int id)
{
var movie = _movieService.GetMovie(id);
var resource = MapToResource(movie, true);
return resource;
}
protected MovieResource MapToResource(Core.Tv.Movie episode, bool includeSeries)
{
var resource = episode.ToResource();
if (includeSeries)
{
var series = episode ?? _movieService.GetMovie(episode.Id);
resource = series.ToResource();
}
return resource;
}
public void Handle(MovieGrabbedEvent message)
{
var resource = message.Movie.Movie.ToResource();
//add a grabbed field in MovieResource?
//resource.Grabbed = true;
BroadcastResourceChange(ModelAction.Updated, resource);
}
public void Handle(MovieDownloadedEvent message)
{
var resource = message.Movie.Movie.ToResource();
BroadcastResourceChange(ModelAction.Updated, resource);
}
}
}
+10 -7
View File
@@ -1,16 +1,17 @@
using NzbDrone.Api.ClientSchema; using FluentValidation;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Core.NetImport; using NzbDrone.Core.NetImport;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Api.NetImport namespace NzbDrone.Api.NetImport
{ {
public class NetImportModule : ProviderModuleBase<NetImportResource, INetImport, NetImportDefinition> public class NetImportModule : ProviderModuleBase<NetImportResource, INetImport, NetImportDefinition>
{ {
private readonly IProfileService _profileService; public NetImportModule(NetImportFactory netImportFactory) : base(netImportFactory, "netimport")
public NetImportModule(NetImportFactory indexerFactory, IProfileService profileService)
: base(indexerFactory, "netimport")
{ {
_profileService = profileService; PostValidator.RuleFor(c => c.RootFolderPath).NotNull();
PostValidator.RuleFor(c => c.MinimumAvailability).NotNull();
PostValidator.RuleFor(c => c.ProfileId).NotNull();
} }
protected override void MapToResource(NetImportResource resource, NetImportDefinition definition) protected override void MapToResource(NetImportResource resource, NetImportDefinition definition)
@@ -22,6 +23,7 @@ namespace NzbDrone.Api.NetImport
resource.ProfileId = definition.ProfileId; resource.ProfileId = definition.ProfileId;
resource.RootFolderPath = definition.RootFolderPath; resource.RootFolderPath = definition.RootFolderPath;
resource.ShouldMonitor = definition.ShouldMonitor; resource.ShouldMonitor = definition.ShouldMonitor;
resource.MinimumAvailability = definition.MinimumAvailability;
} }
protected override void MapToModel(NetImportDefinition definition, NetImportResource resource) protected override void MapToModel(NetImportDefinition definition, NetImportResource resource)
@@ -33,6 +35,7 @@ namespace NzbDrone.Api.NetImport
definition.ProfileId = resource.ProfileId; definition.ProfileId = resource.ProfileId;
definition.RootFolderPath = resource.RootFolderPath; definition.RootFolderPath = resource.RootFolderPath;
definition.ShouldMonitor = resource.ShouldMonitor; definition.ShouldMonitor = resource.ShouldMonitor;
definition.MinimumAvailability = resource.MinimumAvailability;
} }
protected override void Validate(NetImportDefinition definition, bool includeWarnings) protected override void Validate(NetImportDefinition definition, bool includeWarnings)
@@ -41,4 +44,4 @@ namespace NzbDrone.Api.NetImport
base.Validate(definition, includeWarnings); base.Validate(definition, includeWarnings);
} }
} }
} }
@@ -1,4 +1,5 @@
using NzbDrone.Core.NetImport; using NzbDrone.Core.NetImport;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.NetImport namespace NzbDrone.Api.NetImport
{ {
@@ -9,5 +10,6 @@ namespace NzbDrone.Api.NetImport
public bool ShouldMonitor { get; set; } public bool ShouldMonitor { get; set; }
public string RootFolderPath { get; set; } public string RootFolderPath { get; set; }
public int ProfileId { get; set; } public int ProfileId { get; set; }
public MovieStatusType MinimumAvailability { get; set; }
} }
} }
+11 -9
View File
@@ -118,8 +118,10 @@
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" /> <Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
<Compile Include="Indexers\ReleaseModuleBase.cs" /> <Compile Include="Indexers\ReleaseModuleBase.cs" />
<Compile Include="Indexers\ReleasePushModule.cs" /> <Compile Include="Indexers\ReleasePushModule.cs" />
<Compile Include="Movies\MovieModuleWithSignalR.cs" />
<Compile Include="Movies\MovieBulkImportModule.cs" />
<Compile Include="Movies\MovieFileModule.cs" /> <Compile Include="Movies\MovieFileModule.cs" />
<Compile Include="Movies\MovieModule.cs" /> <Compile Include="Series\MovieModule.cs" />
<Compile Include="Movies\RenameMovieModule.cs" /> <Compile Include="Movies\RenameMovieModule.cs" />
<Compile Include="Movies\RenameMovieResource.cs" /> <Compile Include="Movies\RenameMovieResource.cs" />
<Compile Include="Movies\MovieEditorModule.cs" /> <Compile Include="Movies\MovieEditorModule.cs" />
@@ -245,7 +247,6 @@
<Compile Include="Series\SeriesEditorModule.cs" /> <Compile Include="Series\SeriesEditorModule.cs" />
<Compile Include="Series\MovieLookupModule.cs" /> <Compile Include="Series\MovieLookupModule.cs" />
<Compile Include="Series\SeriesLookupModule.cs" /> <Compile Include="Series\SeriesLookupModule.cs" />
<Compile Include="Series\MovieModule.cs" />
<Compile Include="Series\SeriesModule.cs" /> <Compile Include="Series\SeriesModule.cs" />
<Compile Include="Series\MovieResource.cs" /> <Compile Include="Series\MovieResource.cs" />
<Compile Include="Series\SeriesResource.cs" /> <Compile Include="Series\SeriesResource.cs" />
@@ -267,6 +268,7 @@
<Compile Include="Wanted\CutoffModule.cs" /> <Compile Include="Wanted\CutoffModule.cs" />
<Compile Include="Wanted\LegacyMissingModule.cs" /> <Compile Include="Wanted\LegacyMissingModule.cs" />
<Compile Include="Wanted\MissingModule.cs" /> <Compile Include="Wanted\MissingModule.cs" />
<Compile Include="Wanted\MovieCutoffModule.cs" />
<Compile Include="Wanted\MovieMissingModule.cs" /> <Compile Include="Wanted\MovieMissingModule.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -295,11 +297,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>
+13 -1
View File
@@ -1,4 +1,6 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
namespace NzbDrone.Api namespace NzbDrone.Api
@@ -11,6 +13,7 @@ namespace NzbDrone.Api
public SortDirection SortDirection { get; set; } public SortDirection SortDirection { get; set; }
public string FilterKey { get; set; } public string FilterKey { get; set; }
public string FilterValue { get; set; } public string FilterValue { get; set; }
public string FilterType { get; set; }
public int TotalRecords { get; set; } public int TotalRecords { get; set; }
public List<TResource> Records { get; set; } public List<TResource> Records { get; set; }
} }
@@ -38,5 +41,14 @@ namespace NzbDrone.Api
return pagingSpec; return pagingSpec;
} }
/*public static Expression<Func<TModel, object>> CreateFilterExpression<TModel>(string filterKey, string filterValue)
{
Type type = typeof(TModel);
ParameterExpression parameterExpression = Expression.Parameter(type, "x");
Expression expressionBody = parameterExpression;
return expressionBody;
}*/
} }
} }
+1 -1
View File
@@ -119,7 +119,7 @@ namespace NzbDrone.Api
resource.Fields = SchemaBuilder.ToSchema(definition.Settings); resource.Fields = SchemaBuilder.ToSchema(definition.Settings);
resource.InfoLink = string.Format("https://github.com/Sonarr/Sonarr/wiki/Supported-{0}#{1}", resource.InfoLink = string.Format("https://github.com/Radarr/Radarr/wiki/Supported-{0}#{1}",
typeof(TProviderResource).Name.Replace("Resource", "s"), typeof(TProviderResource).Name.Replace("Resource", "s"),
definition.Implementation.ToLower()); definition.Implementation.ToLower());
} }
+11 -1
View File
@@ -214,7 +214,10 @@ namespace NzbDrone.Api.REST
private PagingResource<TResource> ReadPagingResourceFromRequest() private PagingResource<TResource> ReadPagingResourceFromRequest()
{ {
int pageSize; int pageSize;
int.TryParse(Request.Query.PageSize.ToString(), out pageSize); if (!int.TryParse(Request.Query.PageSize.ToString(), out pageSize))
{
pageSize = 100000;
}
if (pageSize == 0) pageSize = 10; if (pageSize == 0) pageSize = 10;
int page; int page;
@@ -249,8 +252,15 @@ namespace NzbDrone.Api.REST
{ {
pagingResource.FilterValue = Request.Query.FilterValue.ToString(); pagingResource.FilterValue = Request.Query.FilterValue.ToString();
} }
if (Request.Query.FilterType != null)
{
pagingResource.FilterType = Request.Query.FilterType.ToString();
}
} }
return pagingResource; return pagingResource;
} }
} }
+25 -2
View File
@@ -4,20 +4,44 @@ using NzbDrone.Api.Extensions;
using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource; using NzbDrone.Core.MetadataSource;
using System.Linq; using System.Linq;
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Movie namespace NzbDrone.Api.Movie
{ {
public class MovieLookupModule : NzbDroneRestModule<MovieResource> public class MovieLookupModule : NzbDroneRestModule<MovieResource>
{ {
private readonly ISearchForNewMovie _searchProxy; private readonly ISearchForNewMovie _searchProxy;
private readonly IProvideMovieInfo _movieInfo;
public MovieLookupModule(ISearchForNewMovie searchProxy) public MovieLookupModule(ISearchForNewMovie searchProxy, IProvideMovieInfo movieInfo)
: base("/movies/lookup") : base("/movies/lookup")
{ {
_movieInfo = movieInfo;
_searchProxy = searchProxy; _searchProxy = searchProxy;
Get["/"] = x => Search(); Get["/"] = x => Search();
Get["/tmdb"] = x => SearchByTmdbId();
Get["/imdb"] = x => SearchByImdbId();
} }
private Response SearchByTmdbId()
{
int tmdbId = -1;
if(Int32.TryParse(Request.Query.tmdbId, out tmdbId))
{
var result = _movieInfo.GetMovieInfo(tmdbId, null);
return result.ToResource().AsResponse();
}
throw new BadRequestException("Tmdb Id was not valid");
}
private Response SearchByImdbId()
{
string imdbId = Request.Query.imdbId;
var result = _movieInfo.GetMovieInfo(imdbId);
return result.ToResource().AsResponse();
}
private Response Search() private Response Search()
{ {
@@ -25,7 +49,6 @@ namespace NzbDrone.Api.Movie
return MapToResource(imdbResults).AsResponse(); return MapToResource(imdbResults).AsResponse();
} }
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies) private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
{ {
foreach (var currentSeries in movies) foreach (var currentSeries in movies)
+65 -1
View File
@@ -15,6 +15,7 @@ using NzbDrone.Core.Validation.Paths;
using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Api.Movie namespace NzbDrone.Api.Movie
{ {
@@ -52,6 +53,7 @@ namespace NzbDrone.Api.Movie
_coverMapper = coverMapper; _coverMapper = coverMapper;
GetResourceAll = AllMovie; GetResourceAll = AllMovie;
GetResourcePaged = GetMoviePaged;
GetResourceById = GetMovie; GetResourceById = GetMovie;
CreateResource = AddMovie; CreateResource = AddMovie;
UpdateResource = UpdateMovie; UpdateResource = UpdateMovie;
@@ -104,6 +106,62 @@ namespace NzbDrone.Api.Movie
return MapToResource(movies); return MapToResource(movies);
} }
private PagingResource<MovieResource> GetMoviePaged(PagingResource<MovieResource> pagingResource)
{
var pagingSpec = pagingResource.MapToPagingSpec<MovieResource, Core.Tv.Movie>();
if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false")
{
pagingSpec.FilterExpression = v => v.Monitored == false;
}
else if (pagingResource.FilterKey == "monitored")
{
pagingSpec.FilterExpression = v => v.Monitored == true;
}
if (pagingResource.FilterKey == "status")
{
switch (pagingResource.FilterValue)
{
case "released":
pagingSpec.FilterExpression = v => v.Status == MovieStatusType.Released;
break;
case "inCinemas":
pagingSpec.FilterExpression = v => v.Status == MovieStatusType.InCinemas;
break;
case "announced":
pagingSpec.FilterExpression = v => v.Status == MovieStatusType.Announced;
break;
}
}
if (pagingResource.FilterKey == "downloaded")
{
pagingSpec.FilterExpression = v => v.MovieFileId == 0;
}
if (pagingResource.FilterKey == "title")
{
if (pagingResource.FilterValue == string.Empty || pagingResource.FilterValue == null)
{
pagingSpec.FilterExpression = v => true;
}
else
{
if (pagingResource.FilterType == "contains")
{
pagingSpec.FilterExpression = v => v.CleanTitle.Contains(pagingResource.FilterValue);
}
else
{
pagingSpec.FilterExpression = v => v.CleanTitle == pagingResource.FilterValue;
}
}
}
return ApplyToPage(_moviesService.Paged, pagingSpec, MovieResourceMapper.ToResource);
}
protected MovieResource MapToResource(Core.Tv.Movie movies) protected MovieResource MapToResource(Core.Tv.Movie movies)
{ {
if (movies == null) return null; if (movies == null) return null;
@@ -147,14 +205,20 @@ namespace NzbDrone.Api.Movie
private void DeleteMovie(int id) private void DeleteMovie(int id)
{ {
var deleteFiles = false; var deleteFiles = false;
var addExclusion = false;
var deleteFilesQuery = Request.Query.deleteFiles; var deleteFilesQuery = Request.Query.deleteFiles;
var addExclusionQuery = Request.Query.addExclusion;
if (deleteFilesQuery.HasValue) if (deleteFilesQuery.HasValue)
{ {
deleteFiles = Convert.ToBoolean(deleteFilesQuery.Value); deleteFiles = Convert.ToBoolean(deleteFilesQuery.Value);
} }
if (addExclusionQuery.HasValue)
{
addExclusion = Convert.ToBoolean(addExclusionQuery.Value);
}
_moviesService.DeleteMovie(id, deleteFiles); _moviesService.DeleteMovie(id, deleteFiles, addExclusion);
} }
private void MapCoversToLocal(params MovieResource[] movies) private void MapCoversToLocal(params MovieResource[] movies)
+10 -2
View File
@@ -43,6 +43,9 @@ namespace NzbDrone.Api.Movie
//Editing Only //Editing Only
public bool Monitored { get; set; } public bool Monitored { get; set; }
public MovieStatusType MinimumAvailability { get; set; }
public bool IsAvailable { get; set; }
public int Runtime { get; set; } public int Runtime { get; set; }
public DateTime? LastInfoSync { get; set; } public DateTime? LastInfoSync { get; set; }
public string CleanTitle { get; set; } public string CleanTitle { get; set; }
@@ -129,6 +132,9 @@ namespace NzbDrone.Api.Movie
ProfileId = model.ProfileId, ProfileId = model.ProfileId,
Monitored = model.Monitored, Monitored = model.Monitored,
MinimumAvailability = model.MinimumAvailability,
IsAvailable = model.IsAvailable(),
SizeOnDisk = size, SizeOnDisk = size,
@@ -181,7 +187,8 @@ namespace NzbDrone.Api.Movie
ProfileId = resource.ProfileId, ProfileId = resource.ProfileId,
Monitored = resource.Monitored, Monitored = resource.Monitored,
MinimumAvailability = resource.MinimumAvailability,
Runtime = resource.Runtime, Runtime = resource.Runtime,
LastInfoSync = resource.LastInfoSync, LastInfoSync = resource.LastInfoSync,
CleanTitle = resource.CleanTitle, CleanTitle = resource.CleanTitle,
@@ -210,7 +217,8 @@ namespace NzbDrone.Api.Movie
movie.ProfileId = resource.ProfileId; movie.ProfileId = resource.ProfileId;
movie.Monitored = resource.Monitored; movie.Monitored = resource.Monitored;
movie.MinimumAvailability = resource.MinimumAvailability;
movie.RootFolderPath = resource.RootFolderPath; movie.RootFolderPath = resource.RootFolderPath;
movie.Tags = resource.Tags; movie.Tags = resource.Tags;
movie.AddOptions = resource.AddOptions; movie.AddOptions = resource.AddOptions;
+1 -1
View File
@@ -15,7 +15,7 @@ namespace NzbDrone.Api.Wanted
ISeriesService seriesService, ISeriesService seriesService,
IQualityUpgradableSpecification qualityUpgradableSpecification, IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster) IBroadcastSignalRMessage signalRBroadcaster)
: base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/cutoff") : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/cutoff-old")
{ {
_episodeCutoffService = episodeCutoffService; _episodeCutoffService = episodeCutoffService;
GetResourcePaged = GetCutoffUnmetEpisodes; GetResourcePaged = GetCutoffUnmetEpisodes;
@@ -0,0 +1,42 @@
using NzbDrone.Api.Movie;
using NzbDrone.Api.Movies;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Datastore;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Wanted
{
public class MovieCutoffModule : MovieModuleWithSignalR
{
private readonly IMovieCutoffService _movieCutoffService;
public MovieCutoffModule(IMovieCutoffService movieCutoffService,
IMovieService movieService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(movieService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/cutoff")
{
_movieCutoffService = movieCutoffService;
GetResourcePaged = GetCutoffUnmetMovies;
}
private PagingResource<MovieResource> GetCutoffUnmetMovies(PagingResource<MovieResource> pagingResource)
{
var pagingSpec = pagingResource.MapToPagingSpec<MovieResource, Core.Tv.Movie>("title", SortDirection.Ascending);
if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false")
{
pagingSpec.FilterExpression = v => v.Monitored == false;
}
else
{
pagingSpec.FilterExpression = v => v.Monitored == true;
}
var resource = ApplyToPage(_movieCutoffService.MoviesWhereCutoffUnmet, pagingSpec, v => MapToResource(v, true));
return resource;
}
}
}
+5 -42
View File
@@ -12,16 +12,14 @@ using NzbDrone.Core.Datastore.Events;
namespace NzbDrone.Api.Wanted namespace NzbDrone.Api.Wanted
{ {
class MovieMissingModule : NzbDroneRestModuleWithSignalR<MovieResource, Core.Tv.Movie>, class MovieMissingModule : MovieModuleWithSignalR
IHandle<MovieGrabbedEvent>,
IHandle<MovieDownloadedEvent>
{ {
protected readonly IMovieService _movieService; protected readonly IMovieService _movieService;
public MovieMissingModule(IMovieService movieService, public MovieMissingModule(IMovieService movieService,
IQualityUpgradableSpecification qualityUpgradableSpecification, IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster) IBroadcastSignalRMessage signalRBroadcaster)
: base(signalRBroadcaster, "wanted/missing") : base(movieService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing")
{ {
_movieService = movieService; _movieService = movieService;
@@ -30,48 +28,13 @@ namespace NzbDrone.Api.Wanted
private PagingResource<MovieResource> GetMissingMovies(PagingResource<MovieResource> pagingResource) private PagingResource<MovieResource> GetMissingMovies(PagingResource<MovieResource> pagingResource)
{ {
var pagingSpec = pagingResource.MapToPagingSpec<MovieResource, Core.Tv.Movie>("physicalRelease", SortDirection.Descending); var pagingSpec = pagingResource.MapToPagingSpec<MovieResource, Core.Tv.Movie>("title", SortDirection.Descending);
if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false") pagingSpec.FilterExpression = _movieService.ConstructFilterExpression(pagingResource.FilterKey, pagingResource.FilterValue);
{
pagingSpec.FilterExpression = v => v.Monitored == false;
}
else
{
pagingSpec.FilterExpression = v => v.Monitored == true;
}
var resource = ApplyToPage(_movieService.MoviesWithoutFiles, pagingSpec, v => MapToResource(v, false)); var resource = ApplyToPage(_movieService.MoviesWithoutFiles, pagingSpec, v => MapToResource(v, true));
return resource; return resource;
} }
private MovieResource GetMovie(int id)
{
var movie = _movieService.GetMovie(id);
var resource = MapToResource(movie, true);
return resource;
}
private MovieResource MapToResource(Core.Tv.Movie movie, bool includeMovieFile)
{
var resource = movie.ToResource();
return resource;
}
public void Handle(MovieGrabbedEvent message)
{
var resource = message.Movie.Movie.ToResource();
//add a grabbed field in MovieResource?
//resource.Grabbed = true;
BroadcastResourceChange(ModelAction.Updated, resource);
}
public void Handle(MovieDownloadedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.Movie.Movie.Id);
}
} }
} }
@@ -2,7 +2,7 @@ using System;
namespace NzbDrone.Common.Extensions namespace NzbDrone.Common.Extensions
{ {
public static class Base64Extentions public static class Base64Extensions
{ {
public static string ToBase64(this byte[] bytes) public static string ToBase64(this byte[] bytes)
{ {
@@ -14,4 +14,4 @@ namespace NzbDrone.Common.Extensions
return BitConverter.GetBytes(input).ToBase64(); return BitConverter.GetBytes(input).ToBase64();
} }
} }
} }
@@ -5,11 +5,11 @@ using System.Xml.Linq;
namespace NzbDrone.Common.Extensions namespace NzbDrone.Common.Extensions
{ {
public static class XmlExtentions public static class XmlExtensions
{ {
public static IEnumerable<XElement> FindDecendants(this XContainer container, string localName) public static IEnumerable<XElement> FindDecendants(this XContainer container, string localName)
{ {
return container.Descendants().Where(c => c.Name.LocalName.Equals(localName, StringComparison.InvariantCultureIgnoreCase)); return container.Descendants().Where(c => c.Name.LocalName.Equals(localName, StringComparison.InvariantCultureIgnoreCase));
} }
} }
} }
+2 -2
View File
@@ -145,14 +145,14 @@
<Compile Include="Expansive\Tree.cs" /> <Compile Include="Expansive\Tree.cs" />
<Compile Include="Expansive\TreeNode.cs" /> <Compile Include="Expansive\TreeNode.cs" />
<Compile Include="Expansive\TreeNodeList.cs" /> <Compile Include="Expansive\TreeNodeList.cs" />
<Compile Include="Extensions\Base64Extentions.cs" /> <Compile Include="Extensions\Base64Extensions.cs" />
<Compile Include="Extensions\DateTimeExtensions.cs" /> <Compile Include="Extensions\DateTimeExtensions.cs" />
<Compile Include="Crypto\HashConverter.cs" /> <Compile Include="Crypto\HashConverter.cs" />
<Compile Include="Extensions\Int64Extensions.cs" /> <Compile Include="Extensions\Int64Extensions.cs" />
<Compile Include="Extensions\ObjectExtensions.cs" /> <Compile Include="Extensions\ObjectExtensions.cs" />
<Compile Include="Extensions\StreamExtensions.cs" /> <Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Extensions\UrlExtensions.cs" /> <Compile Include="Extensions\UrlExtensions.cs" />
<Compile Include="Extensions\XmlExtentions.cs" /> <Compile Include="Extensions\XmlExtensions.cs" />
<Compile Include="HashUtil.cs" /> <Compile Include="HashUtil.cs" />
<Compile Include="Http\Dispatchers\CurlHttpDispatcher.cs" /> <Compile Include="Http\Dispatchers\CurlHttpDispatcher.cs" />
<Compile Include="Http\Dispatchers\FallbackHttpDispatcher.cs" /> <Compile Include="Http\Dispatchers\FallbackHttpDispatcher.cs" />
@@ -0,0 +1,75 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using Moq;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv.Events;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.BulkImport
{
[TestFixture]
public class AddMultiMoviesFixture : CoreTest<MovieService>
{
private List<Movie> fakeMovies;
[SetUp]
public void Setup()
{
fakeMovies = Builder<Movie>.CreateListOfSize(3).BuildList();
fakeMovies.ForEach(m =>
{
m.Path = null;
m.RootFolderPath = @"C:\Test\TV";
});
}
[Test]
public void movies_added_event_should_have_proper_path()
{
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.GetMovieFolder(It.IsAny<Movie>(), null))
.Returns((Movie m, NamingConfig n) => m.Title);
var movies = Subject.AddMovies(fakeMovies);
foreach (Movie movie in movies)
{
movie.Path.Should().NotBeNullOrEmpty();
}
//Subject.GetAllMovies().Should().HaveCount(3);
}
[Test]
public void movies_added_should_ignore_already_added()
{
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.GetMovieFolder(It.IsAny<Movie>(), null))
.Returns((Movie m, NamingConfig n) => m.Title);
Mocker.GetMock<IMovieRepository>().Setup(s => s.All()).Returns(new List<Movie> { fakeMovies[0] });
var movies = Subject.AddMovies(fakeMovies);
Mocker.GetMock<IMovieRepository>().Verify(v => v.InsertMany(It.Is<List<Movie>>(l => l.Count == 2)));
}
[Test]
public void movies_added_should_ignore_duplicates()
{
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.GetMovieFolder(It.IsAny<Movie>(), null))
.Returns((Movie m, NamingConfig n) => m.Title);
fakeMovies[2].TmdbId = fakeMovies[0].TmdbId;
var movies = Subject.AddMovies(fakeMovies);
Mocker.GetMock<IMovieRepository>().Verify(v => v.InsertMany(It.Is<List<Movie>>(l => l.Count == 2)));
}
}
}
@@ -1,312 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DataAugmentation.Xem;
using NzbDrone.Core.DataAugmentation.Xem.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.DataAugmentation.SceneNumbering
{
[TestFixture]
public class XemServiceFixture : CoreTest<XemService>
{
private Series _series;
private List<int> _theXemSeriesIds;
private List<XemSceneTvdbMapping> _theXemTvdbMappings;
private List<Episode> _episodes;
[SetUp]
public void SetUp()
{
_series = Builder<Series>.CreateNew()
.With(v => v.TvdbId = 10)
.With(v => v.UseSceneNumbering = false)
.BuildNew();
_theXemSeriesIds = new List<int> { 120 };
Mocker.GetMock<IXemProxy>()
.Setup(v => v.GetXemSeriesIds())
.Returns(_theXemSeriesIds);
_theXemTvdbMappings = new List<XemSceneTvdbMapping>();
Mocker.GetMock<IXemProxy>()
.Setup(v => v.GetSceneTvdbMappings(10))
.Returns(_theXemTvdbMappings);
_episodes = new List<Episode>();
_episodes.Add(new Episode { SeasonNumber = 1, EpisodeNumber = 1 });
_episodes.Add(new Episode { SeasonNumber = 1, EpisodeNumber = 2 });
_episodes.Add(new Episode { SeasonNumber = 2, EpisodeNumber = 1 });
_episodes.Add(new Episode { SeasonNumber = 2, EpisodeNumber = 2 });
_episodes.Add(new Episode { SeasonNumber = 2, EpisodeNumber = 3 });
_episodes.Add(new Episode { SeasonNumber = 2, EpisodeNumber = 4 });
_episodes.Add(new Episode { SeasonNumber = 2, EpisodeNumber = 5 });
_episodes.Add(new Episode { SeasonNumber = 3, EpisodeNumber = 1 });
_episodes.Add(new Episode { SeasonNumber = 3, EpisodeNumber = 2 });
Mocker.GetMock<IEpisodeService>()
.Setup(v => v.GetEpisodeBySeries(It.IsAny<int>()))
.Returns(_episodes);
}
private void GivenTvdbMappings()
{
_theXemSeriesIds.Add(10);
AddTvdbMapping(1, 1, 1, 1, 1, 1); // 1x01 -> 1x01
AddTvdbMapping(2, 1, 2, 2, 1, 2); // 1x02 -> 1x02
AddTvdbMapping(3, 2, 1, 3, 2, 1); // 2x01 -> 2x01
AddTvdbMapping(4, 2, 2, 4, 2, 2); // 2x02 -> 2x02
AddTvdbMapping(5, 2, 3, 5, 2, 3); // 2x03 -> 2x03
AddTvdbMapping(6, 3, 1, 6, 2, 4); // 3x01 -> 2x04
AddTvdbMapping(7, 3, 2, 7, 2, 5); // 3x02 -> 2x05
}
private void GivenExistingMapping()
{
_series.UseSceneNumbering = true;
_episodes[0].SceneSeasonNumber = 1;
_episodes[0].SceneEpisodeNumber = 1;
_episodes[1].SceneSeasonNumber = 1;
_episodes[1].SceneEpisodeNumber = 2;
_episodes[2].SceneSeasonNumber = 2;
_episodes[2].SceneEpisodeNumber = 1;
_episodes[3].SceneSeasonNumber = 2;
_episodes[3].SceneEpisodeNumber = 2;
_episodes[4].SceneSeasonNumber = 2;
_episodes[4].SceneEpisodeNumber = 3;
_episodes[5].SceneSeasonNumber = 3;
_episodes[5].SceneEpisodeNumber = 1;
_episodes[6].SceneSeasonNumber = 3;
_episodes[6].SceneEpisodeNumber = 1;
}
private void AddTvdbMapping(int sceneAbsolute, int sceneSeason, int sceneEpisode, int tvdbAbsolute, int tvdbSeason, int tvdbEpisode)
{
_theXemTvdbMappings.Add(new XemSceneTvdbMapping
{
Scene = new XemValues { Absolute = sceneAbsolute, Season = sceneSeason, Episode = sceneEpisode },
Tvdb = new XemValues { Absolute = tvdbAbsolute, Season = tvdbSeason, Episode = tvdbEpisode },
});
}
[Test]
public void should_not_fetch_scenenumbering_if_not_listed()
{
Subject.Handle(new SeriesUpdatedEvent(_series));
Mocker.GetMock<IXemProxy>()
.Verify(v => v.GetSceneTvdbMappings(10), Times.Never());
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.IsAny<Series>()), Times.Never());
}
[Test]
public void should_fetch_scenenumbering()
{
GivenTvdbMappings();
Subject.Handle(new SeriesUpdatedEvent(_series));
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.Is<Series>(s => s.UseSceneNumbering == true)), Times.Once());
}
[Test]
public void should_clear_scenenumbering_if_removed_from_thexem()
{
GivenExistingMapping();
Subject.Handle(new SeriesUpdatedEvent(_series));
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.IsAny<Series>()), Times.Once());
}
[Test]
public void should_not_clear_scenenumbering_if_no_results_at_all_from_thexem()
{
GivenExistingMapping();
_theXemSeriesIds.Clear();
Subject.Handle(new SeriesUpdatedEvent(_series));
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.IsAny<Series>()), Times.Never());
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_not_clear_scenenumbering_if_thexem_throws()
{
GivenExistingMapping();
Mocker.GetMock<IXemProxy>()
.Setup(v => v.GetXemSeriesIds())
.Throws(new InvalidOperationException());
Subject.Handle(new SeriesUpdatedEvent(_series));
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.IsAny<Series>()), Times.Never());
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_flag_unknown_future_episodes_if_existing_season_is_mapped()
{
GivenTvdbMappings();
_theXemTvdbMappings.RemoveAll(v => v.Tvdb.Season == 2 && v.Tvdb.Episode == 5);
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 2 && v.EpisodeNumber == 5);
episode.UnverifiedSceneNumbering.Should().BeTrue();
}
[Test]
public void should_flag_unknown_future_season_if_future_season_is_shifted()
{
GivenTvdbMappings();
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 3 && v.EpisodeNumber == 1);
episode.UnverifiedSceneNumbering.Should().BeTrue();
}
[Test]
public void should_not_flag_unknown_future_season_if_future_season_is_not_shifted()
{
GivenTvdbMappings();
_theXemTvdbMappings.RemoveAll(v => v.Scene.Season == 3);
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 3 && v.EpisodeNumber == 1);
episode.UnverifiedSceneNumbering.Should().BeFalse();
}
[Test]
public void should_not_flag_past_episodes_if_not_causing_overlaps()
{
GivenTvdbMappings();
_theXemTvdbMappings.RemoveAll(v => v.Scene.Season == 2);
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 2 && v.EpisodeNumber == 1);
episode.UnverifiedSceneNumbering.Should().BeFalse();
}
[Test]
public void should_flag_past_episodes_if_causing_overlap()
{
GivenTvdbMappings();
_theXemTvdbMappings.RemoveAll(v => v.Scene.Season == 2 && v.Tvdb.Episode <= 1);
_theXemTvdbMappings.First(v => v.Scene.Season == 2 && v.Scene.Episode == 2).Scene.Episode = 1;
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 2 && v.EpisodeNumber == 1);
episode.UnverifiedSceneNumbering.Should().BeTrue();
}
[Test]
public void should_not_extrapolate_season_with_specials()
{
GivenTvdbMappings();
var specialMapping = _theXemTvdbMappings.First(v => v.Tvdb.Season == 2 && v.Tvdb.Episode == 5);
specialMapping.Tvdb.Season = 0;
specialMapping.Tvdb.Episode = 1;
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 2 && v.EpisodeNumber == 5);
episode.UnverifiedSceneNumbering.Should().BeTrue();
episode.SceneSeasonNumber.Should().NotHaveValue();
episode.SceneEpisodeNumber.Should().NotHaveValue();
}
[Test]
public void should_extrapolate_season_with_future_episodes()
{
GivenTvdbMappings();
_theXemTvdbMappings.RemoveAll(v => v.Tvdb.Season == 2 && v.Tvdb.Episode == 5);
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 2 && v.EpisodeNumber == 5);
episode.UnverifiedSceneNumbering.Should().BeTrue();
episode.SceneSeasonNumber.Should().Be(3);
episode.SceneEpisodeNumber.Should().Be(2);
}
[Test]
public void should_extrapolate_season_with_shifted_episodes()
{
GivenTvdbMappings();
_theXemTvdbMappings.RemoveAll(v => v.Tvdb.Season == 2 && v.Tvdb.Episode == 5);
var dualMapping = _theXemTvdbMappings.First(v => v.Tvdb.Season == 2 && v.Tvdb.Episode == 4);
dualMapping.Scene.Season = 2;
dualMapping.Scene.Episode = 3;
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 2 && v.EpisodeNumber == 5);
episode.UnverifiedSceneNumbering.Should().BeTrue();
episode.SceneSeasonNumber.Should().Be(2);
episode.SceneEpisodeNumber.Should().Be(4);
}
[Test]
public void should_extrapolate_shifted_future_seasons()
{
GivenTvdbMappings();
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 3 && v.EpisodeNumber == 2);
episode.UnverifiedSceneNumbering.Should().BeTrue();
episode.SceneSeasonNumber.Should().Be(4);
episode.SceneEpisodeNumber.Should().Be(2);
}
[Test]
public void should_not_extrapolate_matching_future_seasons()
{
GivenTvdbMappings();
_theXemTvdbMappings.RemoveAll(v => v.Scene.Season != 1);
Subject.Handle(new SeriesUpdatedEvent(_series));
var episode = _episodes.First(v => v.SeasonNumber == 3 && v.EpisodeNumber == 2);
episode.UnverifiedSceneNumbering.Should().BeFalse();
episode.SceneSeasonNumber.Should().NotHaveValue();
episode.SceneEpisodeNumber.Should().NotHaveValue();
}
}
}
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
{ {
private Profile _profile; private Profile _profile;
private DelayProfile _delayProfile; private DelayProfile _delayProfile;
private RemoteEpisode _remoteEpisode; private RemoteMovie _remoteEpisode;
[SetUp] [SetUp]
public void Setup() public void Setup()
@@ -38,12 +38,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
.With(d => d.PreferredProtocol = DownloadProtocol.Usenet) .With(d => d.PreferredProtocol = DownloadProtocol.Usenet)
.Build(); .Build();
var series = Builder<Series>.CreateNew() var series = Builder<Movie>.CreateNew()
.With(s => s.Profile = _profile) .With(s => s.Profile = _profile)
.Build(); .Build();
_remoteEpisode = Builder<RemoteEpisode>.CreateNew() _remoteEpisode = Builder<RemoteMovie>.CreateNew()
.With(r => r.Series = series) .With(r => r.Movie = series)
.Build(); .Build();
_profile.Items = new List<ProfileQualityItem>(); _profile.Items = new List<ProfileQualityItem>();
@@ -53,30 +53,30 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
_profile.Cutoff = Quality.WEBDL720p; _profile.Cutoff = Quality.WEBDL720p;
_remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); _remoteEpisode.ParsedMovieInfo = new ParsedMovieInfo();
_remoteEpisode.Release = new ReleaseInfo(); _remoteEpisode.Release = new ReleaseInfo();
_remoteEpisode.Release.DownloadProtocol = DownloadProtocol.Usenet; _remoteEpisode.Release.DownloadProtocol = DownloadProtocol.Usenet;
_remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1).Build().ToList(); //_remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1).Build().ToList();
_remoteEpisode.Episodes.First().EpisodeFileId = 0; //_remoteEpisode.Episodes.First().EpisodeFileId = 0;
Mocker.GetMock<IDelayProfileService>() //Mocker.GetMock<IDelayProfileService>()
.Setup(s => s.BestForTags(It.IsAny<HashSet<int>>())) // .Setup(s => s.BestForTags(It.IsAny<HashSet<int>>()))
.Returns(_delayProfile); // .Returns(_delayProfile);
Mocker.GetMock<IPendingReleaseService>() //Mocker.GetMock<IPendingReleaseService>()
.Setup(s => s.GetPendingRemoteEpisodes(It.IsAny<int>())) // .Setup(s => s.GetPendingRemoteEpisodes(It.IsAny<int>()))
.Returns(new List<RemoteEpisode>()); // .Returns(new List<RemoteEpisode>());
} }
private void GivenExistingFile(QualityModel quality) private void GivenExistingFile(QualityModel quality)
{ {
_remoteEpisode.Episodes.First().EpisodeFileId = 1; //_remoteEpisode.Episodes.First().EpisodeFileId = 1;
_remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded<EpisodeFile>(new EpisodeFile //_remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded<EpisodeFile>(new EpisodeFile
{ // {
Quality = quality // Quality = quality
}); // });
} }
private void GivenUpgradeForExistingFile() private void GivenUpgradeForExistingFile()
@@ -74,9 +74,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
.Returns(1000000); .Returns(1000000);
} }
protected override RemoteEpisode CreateRemoteEpisode() protected override RemoteMovie CreateRemoteMovie()
{ {
var remoteEpisode = base.CreateRemoteEpisode(); var remoteEpisode = base.CreateRemoteMovie();
var torrentInfo = new TorrentInfo(); var torrentInfo = new TorrentInfo();
torrentInfo.Title = remoteEpisode.Release.Title; torrentInfo.Title = remoteEpisode.Release.Title;
@@ -125,7 +125,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
[Test] [Test]
public void Download_should_download_file_if_it_doesnt_exist() public void Download_should_download_file_if_it_doesnt_exist()
{ {
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
Subject.Download(remoteEpisode); Subject.Download(remoteEpisode);
@@ -139,7 +139,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
{ {
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true; Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.DownloadUrl = null; remoteEpisode.Release.DownloadUrl = null;
Subject.Download(remoteEpisode); Subject.Download(remoteEpisode);
@@ -153,7 +153,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
[Test] [Test]
public void Download_should_not_save_magnet_if_disabled() public void Download_should_not_save_magnet_if_disabled()
{ {
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.DownloadUrl = null; remoteEpisode.Release.DownloadUrl = null;
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteEpisode)); Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteEpisode));
@@ -169,7 +169,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
{ {
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true; Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
Subject.Download(remoteEpisode); Subject.Download(remoteEpisode);
@@ -185,7 +185,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]"; var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]";
var expectedFilename = Path.Combine(_blackholeFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV]" + Path.GetExtension(_filePath)); var expectedFilename = Path.Combine(_blackholeFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV]" + Path.GetExtension(_filePath));
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.Title = illegalTitle; remoteEpisode.Release.Title = illegalTitle;
Subject.Download(remoteEpisode); Subject.Download(remoteEpisode);
@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
[Test] [Test]
public void Download_should_throw_if_magnet_and_torrent_url_does_not_exist() public void Download_should_throw_if_magnet_and_torrent_url_does_not_exist()
{ {
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.DownloadUrl = null; remoteEpisode.Release.DownloadUrl = null;
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteEpisode)); Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteEpisode));
@@ -273,7 +273,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
[Test] [Test]
public void should_return_null_hash() public void should_return_null_hash()
{ {
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
Subject.Download(remoteEpisode).Should().BeNull(); Subject.Download(remoteEpisode).Should().BeNull();
} }
@@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
[Test] [Test]
public void Download_should_download_file_if_it_doesnt_exist() public void Download_should_download_file_if_it_doesnt_exist()
{ {
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
Subject.Download(remoteEpisode); Subject.Download(remoteEpisode);
@@ -119,7 +119,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]"; var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]";
var expectedFilename = Path.Combine(_blackholeFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV]" + Path.GetExtension(_filePath)); var expectedFilename = Path.Combine(_blackholeFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV]" + Path.GetExtension(_filePath));
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.Title = illegalTitle; remoteEpisode.Release.Title = illegalTitle;
Subject.Download(remoteEpisode); Subject.Download(remoteEpisode);
@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -208,7 +208,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.DownloadUrl = magnetUrl; remoteEpisode.Release.DownloadUrl = magnetUrl;
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -30,8 +30,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
.Returns(30); .Returns(30);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), (SearchCriteriaBase)null)) .Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), (SearchCriteriaBase)null))
.Returns(() => CreateRemoteEpisode()); .Returns(() => CreateRemoteMovie());
Mocker.GetMock<IHttpClient>() Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>())) .Setup(s => s.Get(It.IsAny<HttpRequest>()))
@@ -42,20 +42,20 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
.Returns<string, OsPath>((h, r) => r); .Returns<string, OsPath>((h, r) => r);
} }
protected virtual RemoteEpisode CreateRemoteEpisode() protected virtual RemoteMovie CreateRemoteMovie()
{ {
var remoteEpisode = new RemoteEpisode(); var remoteEpisode = new RemoteMovie();
remoteEpisode.Release = new ReleaseInfo(); remoteEpisode.Release = new ReleaseInfo();
remoteEpisode.Release.Title = _title; remoteEpisode.Release.Title = _title;
remoteEpisode.Release.DownloadUrl = _downloadUrl; remoteEpisode.Release.DownloadUrl = _downloadUrl;
remoteEpisode.Release.DownloadProtocol = Subject.Protocol; remoteEpisode.Release.DownloadProtocol = Subject.Protocol;
remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); remoteEpisode.ParsedMovieInfo = new ParsedMovieInfo();
remoteEpisode.ParsedEpisodeInfo.FullSeason = false; //remoteEpisode.ParsedEpisodeInfo.FullSeason = false;
remoteEpisode.Episodes = new List<Episode>(); //remoteEpisode.Episodes = new List<Episode>();
remoteEpisode.Series = new Series(); remoteEpisode.Movie = new Movie();
return remoteEpisode; return remoteEpisode;
} }
@@ -0,0 +1,74 @@
using System;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Download.Clients.DownloadStation;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{
[TestFixture]
public class SerialNumberProviderFixture : CoreTest<SerialNumberProvider>
{
protected DownloadStationSettings _settings;
[SetUp]
protected void Setup()
{
_settings = new DownloadStationSettings();
}
private void GivenValidResponse()
{
Mocker.GetMock<IDSMInfoProxy>()
.Setup(d => d.GetSerialNumber(It.IsAny<DownloadStationSettings>()))
.Returns("serial");
}
private void GivenInvalidResponse()
{
Mocker.GetMock<IDSMInfoProxy>()
.Setup(d => d.GetSerialNumber(It.IsAny<DownloadStationSettings>()))
.Throws(new DownloadClientException("Serial response invalid"));
}
[Test]
public void should_return_hashedserialnumber()
{
GivenValidResponse();
var serial = Subject.GetSerialNumber(_settings);
// This hash should remain the same for 'serial', so don't update the test if you change HashConverter, fix the code instead.
serial.Should().Be("50DE66B735D30738618568294742FCF1DFA52A47");
Mocker.GetMock<IDSMInfoProxy>()
.Verify(d => d.GetSerialNumber(It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void should_cache_serialnumber()
{
GivenValidResponse();
var serial1 = Subject.GetSerialNumber(_settings);
var serial2 = Subject.GetSerialNumber(_settings);
serial2.Should().Be(serial1);
Mocker.GetMock<IDSMInfoProxy>()
.Verify(d => d.GetSerialNumber(It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void should_throw_if_serial_number_unavailable()
{
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetSerialNumber(_settings));
ExceptionVerification.ExpectedWarns(1);
}
}
}
@@ -0,0 +1,75 @@
using System;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Download.Clients.DownloadStation;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{
[TestFixture]
public class SharedFolderResolverFixture : CoreTest<SharedFolderResolver>
{
protected string _serialNumber = "SERIALNUMBER";
protected OsPath _sharedFolder;
protected OsPath _physicalPath;
protected DownloadStationSettings _settings;
[SetUp]
protected void Setup()
{
_sharedFolder = new OsPath("/myFolder");
_physicalPath = new OsPath("/mnt/sda1/folder");
_settings = new DownloadStationSettings();
Mocker.GetMock<IFileStationProxy>()
.Setup(f => f.GetSharedFolderMapping(It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Throws(new DownloadClientException("There is no shared folder"));
Mocker.GetMock<IFileStationProxy>()
.Setup(f => f.GetSharedFolderMapping(_sharedFolder.FullPath, It.IsAny<DownloadStationSettings>()))
.Returns(new SharedFolderMapping(_sharedFolder.FullPath, _physicalPath.FullPath));
}
[Test]
public void should_throw_when_cannot_resolve_shared_folder()
{
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.RemapToFullPath(new OsPath("/unknownFolder"), _settings, _serialNumber));
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_return_valid_sharedfolder()
{
var mapping = Subject.RemapToFullPath(_sharedFolder, _settings, "abc");
mapping.Should().Be(_physicalPath);
Mocker.GetMock<IFileStationProxy>()
.Verify(f => f.GetSharedFolderMapping(It.IsAny<string>(), It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void should_cache_mapping()
{
Subject.RemapToFullPath(_sharedFolder, _settings, "abc");
Subject.RemapToFullPath(_sharedFolder, _settings, "abc");
Mocker.GetMock<IFileStationProxy>()
.Verify(f => f.GetSharedFolderMapping(It.IsAny<string>(), It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void should_remap_subfolder()
{
var mapping = Subject.RemapToFullPath(_sharedFolder + "sub", _settings, "abc");
mapping.Should().Be(_physicalPath + "sub");
}
}
}
@@ -0,0 +1,622 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.DownloadStation;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{
[TestFixture]
public class TorrentDownloadStationFixture : DownloadClientFixtureBase<TorrentDownloadStation>
{
protected DownloadStationSettings _settings;
protected DownloadStationTask _queued;
protected DownloadStationTask _downloading;
protected DownloadStationTask _failed;
protected DownloadStationTask _completed;
protected DownloadStationTask _seeding;
protected DownloadStationTask _magnet;
protected DownloadStationTask _singleFile;
protected DownloadStationTask _multipleFiles;
protected DownloadStationTask _singleFileCompleted;
protected DownloadStationTask _multipleFilesCompleted;
protected string _serialNumber = "SERIALNUMBER";
protected string _category = "sonarr";
protected string _tvDirectory = @"video/Series";
protected string _defaultDestination = "somepath";
protected OsPath _physicalPath = new OsPath("/mnt/sdb1/mydata");
protected Dictionary<string, object> _downloadStationConfigItems;
protected string DownloadURL => "magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcad53426&dn=download";
[SetUp]
public void Setup()
{
_settings = new DownloadStationSettings()
{
Host = "127.0.0.1",
Port = 5000,
Username = "admin",
Password = "pass"
};
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = _settings;
_queued = new DownloadStationTask()
{
Id = "id1",
Size = 1000,
Status = DownloadStationTaskStatus.Waiting,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", DownloadURL }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "0"},
{ "speed_download", "0" }
}
}
};
_completed = new DownloadStationTask()
{
Id = "id2",
Size = 1000,
Status = DownloadStationTaskStatus.Finished,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", DownloadURL }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
},
}
};
_seeding = new DownloadStationTask()
{
Id = "id2",
Size = 1000,
Status = DownloadStationTaskStatus.Seeding,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", DownloadURL }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
}
}
};
_downloading = new DownloadStationTask()
{
Id = "id3",
Size = 1000,
Status = DownloadStationTaskStatus.Downloading,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", DownloadURL }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "100"},
{ "speed_download", "50" }
}
}
};
_failed = new DownloadStationTask()
{
Id = "id4",
Size = 1000,
Status = DownloadStationTaskStatus.Error,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", DownloadURL }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "10"},
{ "speed_download", "0" }
}
}
};
_singleFile = new DownloadStationTask()
{
Id = "id5",
Size = 1000,
Status = DownloadStationTaskStatus.Seeding,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "a.mkv",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", DownloadURL }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
}
}
};
_multipleFiles = new DownloadStationTask()
{
Id = "id6",
Size = 1000,
Status = DownloadStationTaskStatus.Seeding,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", DownloadURL }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
}
}
};
_singleFileCompleted = new DownloadStationTask()
{
Id = "id6",
Size = 1000,
Status = DownloadStationTaskStatus.Finished,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "a.mkv",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", DownloadURL }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
}
}
};
_multipleFilesCompleted = new DownloadStationTask()
{
Id = "id6",
Size = 1000,
Status = DownloadStationTaskStatus.Finished,
Type = DownloadStationTaskType.BT.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", DownloadURL }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
}
}
};
Mocker.GetMock<ITorrentFileInfoReader>()
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<byte[]>()))
.Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951");
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
_downloadStationConfigItems = new Dictionary<string, object>
{
{ "default_destination", _defaultDestination },
};
Mocker.GetMock<IDownloadStationProxy>()
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
.Returns(_downloadStationConfigItems);
}
protected void GivenSharedFolder()
{
Mocker.GetMock<ISharedFolderResolver>()
.Setup(s => s.RemapToFullPath(It.IsAny<OsPath>(), It.IsAny<DownloadStationSettings>(), It.IsAny<string>()))
.Returns<OsPath, DownloadStationSettings, string>((path, setttings, serial) => _physicalPath);
}
protected void GivenSerialNumber()
{
Mocker.GetMock<ISerialNumberProvider>()
.Setup(s => s.GetSerialNumber(It.IsAny<DownloadStationSettings>()))
.Returns(_serialNumber);
}
protected void GivenTvCategory()
{
_settings.TvCategory = _category;
}
protected void GivenTvDirectory()
{
_settings.TvDirectory = _tvDirectory;
}
protected virtual void GivenTasks(List<DownloadStationTask> torrents)
{
if (torrents == null)
{
torrents = new List<DownloadStationTask>();
}
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
.Returns(torrents);
}
protected void PrepareClientToReturnQueuedItem()
{
GivenTasks(new List<DownloadStationTask>
{
_queued
});
}
protected void GivenSuccessfulDownload()
{
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
}
protected override RemoteMovie CreateRemoteMovie()
{
var episode = base.CreateRemoteMovie();
episode.Release.DownloadUrl = DownloadURL;
return episode;
}
protected int GivenAllKindOfTasks()
{
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
Mocker.GetMock<IDownloadStationProxy>()
.Setup(d => d.GetTasks(_settings))
.Returns(tasks);
return tasks.Count;
}
[Test]
public void Download_with_TvDirectory_should_force_directory()
{
GivenSerialNumber();
GivenTvDirectory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void Download_with_category_should_force_directory()
{
GivenSerialNumber();
GivenTvCategory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void Download_without_TvDirectory_and_Category_should_use_default()
{
GivenSerialNumber();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void GetItems_should_return_empty_list_if_no_tasks_available()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>());
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_return_ignore_tasks_of_unknown_type()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask> { _completed });
_completed.Type = "ipfs";
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_ignore_downloads_in_wrong_folder()
{
_settings.TvDirectory = @"/shared/folder/sub";
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask> { _completed });
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_throw_if_shared_folder_resolve_fails()
{
Mocker.GetMock<ISharedFolderResolver>()
.Setup(s => s.RemapToFullPath(It.IsAny<OsPath>(), It.IsAny<DownloadStationSettings>(), It.IsAny<string>()))
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
GivenSerialNumber();
GivenAllKindOfTasks();
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetItems());
ExceptionVerification.ExpectedErrors(0);
}
[Test]
public void GetItems_should_throw_if_serial_number_unavailable()
{
Mocker.GetMock<ISerialNumberProvider>()
.Setup(s => s.GetSerialNumber(_settings))
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
GivenSharedFolder();
GivenAllKindOfTasks();
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetItems());
ExceptionVerification.ExpectedErrors(0);
}
[Test]
public void Download_should_throw_and_not_add_task_if_cannot_get_serial_number()
{
var remoteEpisode = CreateRemoteMovie();
Mocker.GetMock<ISerialNumberProvider>()
.Setup(s => s.GetSerialNumber(_settings))
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode));
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
}
[Test]
public void GetItems_should_set_outputPath_to_base_folder_when_single_file_non_finished_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>() { _singleFile });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be(_physicalPath + _singleFile.Title);
}
[Test]
public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_non_finished_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>() { _multipleFiles });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be(_physicalPath + _multipleFiles.Title);
}
[Test]
public void GetItems_should_set_outputPath_to_base_folder_when_single_file_finished_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>() { _singleFileCompleted });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be(_physicalPath + _singleFileCompleted.Title);
}
[Test]
public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_finished_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>() { _multipleFilesCompleted });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be($"{_physicalPath}/{_multipleFiles.Title}");
}
[Test]
public void GetItems_should_not_map_outputpath_for_queued_or_downloading_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>
{
_queued, _downloading
});
var items = Subject.GetItems();
items.Should().HaveCount(2);
items.Should().OnlyContain(v => v.OutputPath.IsEmpty);
}
[Test]
public void GetItems_should_map_outputpath_for_completed_or_failed_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>
{
_completed, _failed, _seeding
});
var items = Subject.GetItems();
items.Should().HaveCount(3);
items.Should().OnlyContain(v => !v.OutputPath.IsEmpty);
}
[TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading, true)]
[TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed, false)]
[TestCase(DownloadStationTaskStatus.Seeding, DownloadItemStatus.Completed, true)]
[TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued, true)]
public void GetItems_should_return_readonly_expected(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus, bool readOnlyExpected)
{
GivenSerialNumber();
GivenSharedFolder();
_queued.Status = apiStatus;
GivenTasks(new List<DownloadStationTask>() { _queued });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().IsReadOnly.Should().Be(readOnlyExpected);
}
[TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.Error, DownloadItemStatus.Failed)]
[TestCase(DownloadStationTaskStatus.Extracting, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed)]
[TestCase(DownloadStationTaskStatus.Finishing, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.HashChecking, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.Paused, DownloadItemStatus.Paused)]
[TestCase(DownloadStationTaskStatus.Seeding, DownloadItemStatus.Completed)]
[TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued)]
public void GetItems_should_return_item_as_downloadItemStatus(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus)
{
GivenSerialNumber();
GivenSharedFolder();
_queued.Status = apiStatus;
GivenTasks(new List<DownloadStationTask>() { _queued });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().Status.Should().Be(expectedItemStatus);
}
}
}
@@ -0,0 +1,452 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.DownloadStation;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Test.Common;
using NzbDrone.Core.Organizer;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
{
[TestFixture]
public class UsenetDownloadStationFixture : DownloadClientFixtureBase<UsenetDownloadStation>
{
protected DownloadStationSettings _settings;
protected DownloadStationTask _queued;
protected DownloadStationTask _downloading;
protected DownloadStationTask _failed;
protected DownloadStationTask _completed;
protected DownloadStationTask _seeding;
protected string _serialNumber = "SERIALNUMBER";
protected string _category = "sonarr";
protected string _tvDirectory = @"video/Series";
protected string _defaultDestination = "somepath";
protected OsPath _physicalPath = new OsPath("/mnt/sdb1/mydata");
protected RemoteMovie _remoteEpisode;
protected Dictionary<string, object> _downloadStationConfigItems;
[SetUp]
public void Setup()
{
_remoteEpisode = CreateRemoteMovie();
_settings = new DownloadStationSettings()
{
Host = "127.0.0.1",
Port = 5000,
Username = "admin",
Password = "pass"
};
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = _settings;
_queued = new DownloadStationTask()
{
Id = "id1",
Size = 1000,
Status = DownloadStationTaskStatus.Waiting,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "0"},
{ "speed_download", "0" }
}
}
};
_completed = new DownloadStationTask()
{
Id = "id2",
Size = 1000,
Status = DownloadStationTaskStatus.Finished,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
},
}
};
_seeding = new DownloadStationTask()
{
Id = "id2",
Size = 1000,
Status = DownloadStationTaskStatus.Seeding,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "1000"},
{ "speed_download", "0" }
}
}
};
_downloading = new DownloadStationTask()
{
Id = "id3",
Size = 1000,
Status = DownloadStationTaskStatus.Downloading,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "100"},
{ "speed_download", "50" }
}
}
};
_failed = new DownloadStationTask()
{
Id = "id4",
Size = 1000,
Status = DownloadStationTaskStatus.Error,
Type = DownloadStationTaskType.NZB.ToString(),
Username = "admin",
Title = "title",
Additional = new DownloadStationTaskAdditional
{
Detail = new Dictionary<string, string>
{
{ "destination","shared/folder" },
{ "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" }
},
Transfer = new Dictionary<string, string>
{
{ "size_downloaded", "10"},
{ "speed_download", "0" }
}
}
};
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
_downloadStationConfigItems = new Dictionary<string, object>
{
{ "default_destination", _defaultDestination },
};
Mocker.GetMock<IDownloadStationProxy>()
.Setup(v => v.GetConfig(It.IsAny<DownloadStationSettings>()))
.Returns(_downloadStationConfigItems);
}
protected void GivenSharedFolder()
{
Mocker.GetMock<ISharedFolderResolver>()
.Setup(s => s.RemapToFullPath(It.IsAny<OsPath>(), It.IsAny<DownloadStationSettings>(), It.IsAny<string>()))
.Returns<OsPath, DownloadStationSettings, string>((path, setttings, serial) => _physicalPath);
}
protected void GivenSerialNumber()
{
Mocker.GetMock<ISerialNumberProvider>()
.Setup(s => s.GetSerialNumber(It.IsAny<DownloadStationSettings>()))
.Returns(_serialNumber);
}
protected void GivenTvCategory()
{
_settings.TvCategory = _category;
}
protected void GivenTvDirectory()
{
_settings.TvDirectory = _tvDirectory;
}
protected virtual void GivenTasks(List<DownloadStationTask> nzbs)
{
if (nzbs == null)
{
nzbs = new List<DownloadStationTask>();
}
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.GetTasks(It.IsAny<DownloadStationSettings>()))
.Returns(nzbs);
}
protected void PrepareClientToReturnQueuedItem()
{
GivenTasks(new List<DownloadStationTask>
{
_queued
});
}
protected void GivenSuccessfulDownload()
{/*
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
*/
Mocker.GetMock<IDownloadStationProxy>()
.Setup(s => s.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DownloadStationSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
}
protected void GivenAllKindOfTasks()
{
var tasks = new List<DownloadStationTask>() { _queued, _completed, _failed, _downloading, _seeding };
Mocker.GetMock<IDownloadStationProxy>()
.Setup(d => d.GetTasks(_settings))
.Returns(tasks);
}
[Test]
public void Download_with_TvDirectory_should_force_directory()
{
GivenSerialNumber();
GivenTvDirectory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), _tvDirectory, It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void Download_with_category_should_force_directory()
{
GivenSerialNumber();
GivenTvCategory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), $"{_defaultDestination}/{_category}", It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void Download_without_TvDirectory_and_Category_should_use_default()
{
GivenSerialNumber();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromData(It.IsAny<byte[]>(), It.IsAny<string>(), null, It.IsAny<DownloadStationSettings>()), Times.Once());
}
[Test]
public void GetItems_should_return_empty_list_if_no_tasks_available()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>());
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_return_ignore_tasks_of_unknown_type()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask> { _completed });
_completed.Type = "ipfs";
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_ignore_downloads_in_wrong_folder()
{
_settings.TvDirectory = @"/shared/folder/sub";
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask> { _completed });
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_throw_if_shared_folder_resolve_fails()
{
Mocker.GetMock<ISharedFolderResolver>()
.Setup(s => s.RemapToFullPath(It.IsAny<OsPath>(), It.IsAny<DownloadStationSettings>(), It.IsAny<string>()))
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
GivenSerialNumber();
GivenAllKindOfTasks();
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetItems());
ExceptionVerification.ExpectedErrors(0);
}
[Test]
public void GetItems_should_throw_if_serial_number_unavailable()
{
Mocker.GetMock<ISerialNumberProvider>()
.Setup(s => s.GetSerialNumber(_settings))
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
GivenSharedFolder();
GivenAllKindOfTasks();
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.GetItems());
ExceptionVerification.ExpectedErrors(0);
}
[Test]
public void Download_should_throw_and_not_add_task_if_cannot_get_serial_number()
{
var remoteEpisode = CreateRemoteMovie();
Mocker.GetMock<ISerialNumberProvider>()
.Setup(s => s.GetSerialNumber(_settings))
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteEpisode));
Mocker.GetMock<IDownloadStationProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
}
[Test]
public void GetItems_should_not_map_outputpath_for_queued_or_downloading_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>
{
_queued, _downloading
});
var items = Subject.GetItems();
items.Should().HaveCount(2);
items.Should().OnlyContain(v => v.OutputPath.IsEmpty);
}
[Test]
public void GetItems_should_map_outputpath_for_completed_or_failed_tasks()
{
GivenSerialNumber();
GivenSharedFolder();
GivenTasks(new List<DownloadStationTask>
{
_completed, _failed, _seeding
});
var items = Subject.GetItems();
items.Should().HaveCount(3);
items.Should().OnlyContain(v => !v.OutputPath.IsEmpty);
}
[TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading, true)]
[TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed, false)]
[TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued, true)]
public void GetItems_should_return_readonly_expected(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus, bool readOnlyExpected)
{
GivenSerialNumber();
GivenSharedFolder();
_queued.Status = apiStatus;
GivenTasks(new List<DownloadStationTask>() { _queued });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().IsReadOnly.Should().Be(readOnlyExpected);
}
[TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.Error, DownloadItemStatus.Failed)]
[TestCase(DownloadStationTaskStatus.Extracting, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed)]
[TestCase(DownloadStationTaskStatus.Finishing, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.HashChecking, DownloadItemStatus.Downloading)]
[TestCase(DownloadStationTaskStatus.Paused, DownloadItemStatus.Paused)]
[TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued)]
public void GetItems_should_return_item_as_downloadItemStatus(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus)
{
GivenSerialNumber();
GivenSharedFolder();
_queued.Status = apiStatus;
GivenTasks(new List<DownloadStationTask>() { _queued });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().Status.Should().Be(expectedItemStatus);
}
}
}
@@ -197,7 +197,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -276,7 +276,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
[Test] [Test]
public void Download_from_magnet_link_should_return_hash_uppercase() public void Download_from_magnet_link_should_return_hash_uppercase()
{ {
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.DownloadUrl = "magnet:?xt=urn:btih:a45129e59d8750f9da982f53552b1e4f0457ee9f"; remoteEpisode.Release.DownloadUrl = "magnet:?xt=urn:btih:a45129e59d8750f9da982f53552b1e4f0457ee9f";
@@ -291,7 +291,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
[Test] [Test]
public void Download_from_torrent_file_should_return_hash_uppercase() public void Download_from_torrent_file_should_return_hash_uppercase()
{ {
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
Mocker.GetMock<IHadoukenProxy>() Mocker.GetMock<IHadoukenProxy>()
.Setup(v => v.AddTorrentFile(It.IsAny<HadoukenSettings>(), It.IsAny<byte[]>())) .Setup(v => v.AddTorrentFile(It.IsAny<HadoukenSettings>(), It.IsAny<byte[]>()))
@@ -189,7 +189,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -201,7 +201,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
{ {
GivenFailedDownload(); GivenFailedDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
Assert.Throws<DownloadClientException>(() => Subject.Download(remoteEpisode)); Assert.Throws<DownloadClientException>(() => Subject.Download(remoteEpisode));
} }
@@ -303,7 +303,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -315,7 +315,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{ {
GivenFailedDownload(); GivenFailedDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
Assert.Throws<DownloadClientException>(() => Subject.Download(remoteEpisode)); Assert.Throws<DownloadClientException>(() => Subject.Download(remoteEpisode));
} }
@@ -245,7 +245,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -257,7 +257,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.DownloadUrl = magnetUrl; remoteEpisode.Release.DownloadUrl = magnetUrl;
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -290,7 +290,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
GivenRedirectToMagnet(); GivenRedirectToMagnet();
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -303,7 +303,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
GivenRedirectToTorrent(); GivenRedirectToTorrent();
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -116,7 +116,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.Title = title; remoteEpisode.Release.Title = title;
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -274,7 +274,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -315,12 +315,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>())) .Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()))
.Returns(new SabnzbdAddResponse()); .Returns(new SabnzbdAddResponse());
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1) //remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
.All() // .All()
.With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT)) // .With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT))
.Build() // .Build()
.ToList(); // .ToList();
Subject.Download(remoteEpisode); Subject.Download(remoteEpisode);
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -68,7 +68,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
GivenTvDirectory(); GivenTvDirectory();
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
GivenTvCategory(); GivenTvCategory();
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -102,7 +102,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
_transmissionConfigItems["download-dir"] += "/"; _transmissionConfigItems["download-dir"] += "/";
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -117,7 +117,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -132,7 +132,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.DownloadUrl = magnetUrl; remoteEpisode.Release.DownloadUrl = magnetUrl;
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -229,7 +229,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -253,7 +253,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.DownloadUrl = magnetUrl; remoteEpisode.Release.DownloadUrl = magnetUrl;
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -351,7 +351,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
GivenRedirectToMagnet(); GivenRedirectToMagnet();
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -364,7 +364,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
GivenRedirectToTorrent(); GivenRedirectToTorrent();
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -70,7 +70,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
GivenTvDirectory(); GivenTvDirectory();
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -86,7 +86,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
GivenTvCategory(); GivenTvCategory();
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
_transmissionConfigItems["download-dir"] += "/"; _transmissionConfigItems["download-dir"] += "/";
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -119,7 +119,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -134,7 +134,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
{ {
GivenSuccessfulDownload(); GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode(); var remoteEpisode = CreateRemoteMovie();
remoteEpisode.Release.DownloadUrl = magnetUrl; remoteEpisode.Release.DownloadUrl = magnetUrl;
var id = Subject.Download(remoteEpisode); var id = Subject.Download(remoteEpisode);
@@ -20,22 +20,19 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
public class RemoveGrabbedFixture : CoreTest<PendingReleaseService> public class RemoveGrabbedFixture : CoreTest<PendingReleaseService>
{ {
private DownloadDecision _temporarilyRejected; private DownloadDecision _temporarilyRejected;
private Series _series; private Movie _series;
private Episode _episode; private Episode _episode;
private Profile _profile; private Profile _profile;
private ReleaseInfo _release; private ReleaseInfo _release;
private ParsedEpisodeInfo _parsedEpisodeInfo; private ParsedMovieInfo _parsedEpisodeInfo;
private RemoteEpisode _remoteEpisode; private RemoteMovie _remoteEpisode;
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
_series = Builder<Series>.CreateNew() _series = Builder<Movie>.CreateNew()
.Build(); .Build();
_episode = Builder<Episode>.CreateNew()
.Build();
_profile = new Profile _profile = new Profile
{ {
Name = "Test", Name = "Test",
@@ -52,13 +49,13 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
_release = Builder<ReleaseInfo>.CreateNew().Build(); _release = Builder<ReleaseInfo>.CreateNew().Build();
_parsedEpisodeInfo = Builder<ParsedEpisodeInfo>.CreateNew().Build(); _parsedEpisodeInfo = Builder<ParsedMovieInfo>.CreateNew().Build();
_parsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p); _parsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p);
_remoteEpisode = new RemoteEpisode(); _remoteEpisode = new RemoteMovie();
_remoteEpisode.Episodes = new List<Episode>{ _episode }; //_remoteEpisode.Episodes = new List<Episode>{ _episode };
_remoteEpisode.Series = _series; _remoteEpisode.Movie = _series;
_remoteEpisode.ParsedEpisodeInfo = _parsedEpisodeInfo; _remoteEpisode.ParsedMovieInfo = _parsedEpisodeInfo;
_remoteEpisode.Release = _release; _remoteEpisode.Release = _release;
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new Rejection("Temp Rejected", RejectionType.Temporary)); _temporarilyRejected = new DownloadDecision(_remoteEpisode, new Rejection("Temp Rejected", RejectionType.Temporary));
@@ -67,13 +64,13 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
.Setup(s => s.All()) .Setup(s => s.All())
.Returns(new List<PendingRelease>()); .Returns(new List<PendingRelease>());
Mocker.GetMock<ISeriesService>() Mocker.GetMock<IMovieService>()
.Setup(s => s.GetSeries(It.IsAny<int>())) .Setup(s => s.GetMovie(It.IsAny<int>()))
.Returns(_series); .Returns(_series);
Mocker.GetMock<IParsingService>() //Mocker.GetMock<IParsingService>()
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), _series, true, null)) // .Setup(s => s.GetMovie(It.IsAny<ParsedMovieInfo>(), _series.Title))
.Returns(new List<Episode> {_episode}); // .Returns(_episode);
Mocker.GetMock<IPrioritizeDownloadDecision>() Mocker.GetMock<IPrioritizeDownloadDecision>()
.Setup(s => s.PrioritizeDecisions(It.IsAny<List<DownloadDecision>>())) .Setup(s => s.PrioritizeDecisions(It.IsAny<List<DownloadDecision>>()))
@@ -89,7 +86,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
.All() .All()
.With(h => h.SeriesId = _series.Id) .With(h => h.SeriesId = _series.Id)
.With(h => h.Release = _release.JsonClone()) .With(h => h.Release = _release.JsonClone())
.With(h => h.ParsedEpisodeInfo = parsedEpisodeInfo) .With(h => h.ParsedMovieInfo = _parsedEpisodeInfo)
.Build(); .Build();
Mocker.GetMock<IPendingReleaseRepository>() Mocker.GetMock<IPendingReleaseRepository>()
@@ -102,7 +99,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{ {
GivenHeldRelease(_parsedEpisodeInfo.Quality); GivenHeldRelease(_parsedEpisodeInfo.Quality);
Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode)); Subject.Handle(new MovieGrabbedEvent(_remoteEpisode));
VerifyDelete(); VerifyDelete();
} }
@@ -112,7 +109,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{ {
GivenHeldRelease(new QualityModel(Quality.SDTV)); GivenHeldRelease(new QualityModel(Quality.SDTV));
Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode)); Subject.Handle(new MovieGrabbedEvent(_remoteEpisode));
VerifyDelete(); VerifyDelete();
} }
@@ -122,7 +119,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{ {
GivenHeldRelease(new QualityModel(Quality.Bluray720p)); GivenHeldRelease(new QualityModel(Quality.Bluray720p));
Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode)); Subject.Handle(new MovieGrabbedEvent(_remoteEpisode));
VerifyNoDelete(); VerifyNoDelete();
} }
@@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<xhtml:meta xmlns:xhtml="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
<meta xmlns="http://pipes.yahoo.com" name="pipes" content="noprocess" />
<title>TV :: AlphaRatio</title>
<link>https://alpharatio.cc/</link>
<description>Personal RSS feed: TV</description>
<language>en-us</language>
<lastBuildDate>Tue, 29 Nov 2016 11:01:28 +0000</lastBuildDate>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<generator>Gazelle Feed Class</generator>
<item>
<title><![CDATA[TvHD 465989 465960 Good.Behavior.S01E03.PROPER.720p.HDTV.x264-KILLERS]]></title>
<description>
<![CDATA[MB <br />
@@@@: : <br />
:7 :::.7:@.:u7:.X5LF <br />
.LFq2 .B@B@B@B@B@B@B@ <br />
.. i@r rB@B@B@B@B@B@B@@@: <br />
: :B@B@B@B@: X@@@@@B@B@B@B@B@B@B@J .u@B. <br />
:.YkuB@B@B@BM. @B@B@B@B@B@@@B@B@B@r 2B@B@B@B@i <br />
@@@B@r@@@B@B: B@B@B@B@B@@@B@B@B@B@ i@B@B@B@BrO@@@@@ <br />
@@@@B@B@BB, r:@B@B@@@B@@@B@q@@@BM @L:B.B, @@B@B@B@BO@@@@B@B@B <br />
jB@B@B@B@N. 7 B@@@@@B@B@O 8B@. @F B@B@B@@@O@B@B@B@@@@@B. <br />
i@B@B@B@: 7 @B@B@B@B@ B: B: i@B@B@B@BNB8B7 .B@@ <br />
@B@B@. 1G @B@B@B@ @ , @B@:i @u @: 0EB@ <br />
;ir , U@B@B .@ B@B L B@B <br />
7 B@B@ q@Bv:@BP @B@ <br />
i@Bu @ ,S@ @@ B@@@B@B@. BkU@B@ 5Ui @Y@B <br />
@@B@v B :@iB B@@@B@B@M@ @B@@@B@BB @@7i iU 5i2vB@k B@ <br />
@B@B@B7 i @ @B@B@B@B@B@B5 i@B@@@B@ r @ <br />
@B@B@B@. @ B@B@B@B@B@B@B@. MB @Bu . U @Bi <br />
k@P @@@OBi .@ @B@B@B@B@B@B@B. @MBB@@ @F @ 7B@B <br />
@B @B@@@B@ 0B@B@B@B@B@B@B@B@ B@B@B@B@F B@B@B. B@B <br />
B @B@B@B B: B@B@B@B@@@B@@@BM B@B@B@B@B@: @B@;:B@@@: F@B <br />
@. B@@@B@ @Bu i. MX J B@B@B@ @B. @B@B@B@@ B@B@F Si k@@ B@BN <br />
@@ @B@B@B@B@B B @B@BOr: .i0F7@B: B@B@ E@ @B@B@r@ B@B@B. @B@B@B5 <br />
B@B@B@@@B@B@B: @B@B@B@Z: B@B@B@B@B@B@@@B, L@ @B@ B@B@B@B@B@B@B, <br />
:@B@B@B@B@B@B@: Y@B@@@@@B@B@B@: 7B@@@0 :@ L@ ,@B@B@B@B@B@B@B@. <br />
JB@B@B@B@B@B@B@ U@B@@J, @U.@@B@B@B B@F i@ PB @B@B@B@B@BG.@B@B@B, <br />
r@B@B@B@B@B@B@B ; @B@B@ :. @ @J r@ G@ @@: .Z@@7 B@@@@@B@B@B@F <br />
,B@B@B@B@B@B@B@B5 @B@@@ j@B5E@BXB@BvO rB OB B@ B@@@r B@B@B@B@B@B@B@B. <br />
@B@B@B@B@B@@@B@i @@ .uO0 :v. @ @B@B @@@ L: ,@ .@ Z@ iB B@B @B@B@@@BNB@ :2@B@B@B@@@B@B <br />
:@@B@B@B@B@@@B@B..@@@B@B@@. :YY B@@@: B, B .: u@ .@B@B r@ OB i@B@B@@@B@B <br />
UB@B@B@B@B@@@B@B@B@B@B LJ, @B@B. @. @ Y @BP .rUB@B@B@B@Z7, B@B@B@B@B@ <br />
,@B@@@B@B@B@B@B@@@ i17. @B@B@v O, B 1B@B@B@B@@@B@@@B@B@B@B@B@B@B@BiB@Bv<br />
:B@B@B@B@B@B@B@2@B B@B@B@ k. .@ M@@BOB@B@B@B@B@B@B@B@B@B@B@B@B@P @@B<br />
i@B@B@@@B@B@B@B M@B .7 @B@B@r B. 7B @ YB@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@@@<br />
u@@B@B@B@@@B@B@ B@B@ 2U8. 5B@@E @, r@ M B@B@B@B@B@B@B@B@B@@@B@B@B@B@@@B@@@@@B<br />
q@B@B@B@@@B@B@B. @@@i2 @JX :@B@ BY rB @ G@@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@<br />
BB@B@B@B@B@B@Bi . B@@: @B @ @B@B@B@B@B@B@B@B@B@B@B@@@@@B@B@B@j<br />
O@B@B@B@B@B@B@@@ u@B@@ Bu B @@B@B@B@BB8 0B@B@B <br />
SB@B@B@B@B@B@B@B@B @@B@@@B @ .BS r @@@B@ <br />
.@B@B@B@B@B@B@B@B@ B@B@@@B@Br XB@Br 7B u .vB B: B@B@B@B <br />
@@@@B@B@B@B@B@B@B@B@B .@B@B@B@B@@M i..@. i i i P @,jB @@B <br />
@B@B@B@B@B@B@B@B@B@B@B@i @N@@@B@@@B@B@B @ B@E <br />
@B@B@B@@@B@B@B@B@B@B@B@B @ : @@B@B@@@Mi B . @@ <br />
F@B@@@B@B@B@B@B@@@B@B@B@,@ M @.:v X i B :BM <br />
i@B@B@B@B@@@B@B@B@@@@@B@B L ,r , ; B@. @B@, <br />
,M@B@B @B@B@B@B@@@B@B@B@ rL B j :jr@B@@r @B@B@B <br />
@B@B@B@B@B@B@B@B .: . .@ : , @@@@B@@ <br />
,@B@B@B@B@B@B@B .@X r5BMB: r ,, 7 B B@B @B@B@B@@ <br />
L@@B@B@B@ 8B@B@ BM:@B@B@B@B@B@@8X80Mu: FB@B@B@B@B@@@B@B@@@B@B q uO@GMFLv@B@B@B@B@ B@Bu <br />
@B@B@B@B k@ MYM@@@B@B@F ,. :5@B@B7 Y@B@@@@@B@@@B@@@@@B@@@@@B@ B : B@B@B@ @@B@BL <br />
B@@@@@@@ @@7 @EvB@BF B@B@B@@@@@B@BMB@B@B@B@@@B@B@B@ 7 . .@u, i@ B@B@77B@ <br />
B@B@B@@@O@ : 7., :@B@@@B@B@B@, .LB@@@B@B@B@B@B@B@ @ r@: @::M @@B@B@M@B@ <br />
:i @B@B@@@Bi5 :: v@B@@@B@B@B B@@@B@@@B@@@B@@ B@@@B@B@B@B@B@B@i<br />
: B@B@B@B@B@ @B@B@B@@@B LB@B@@@B@B@B@B@B@@5 @B@B@B@B@B@@@B@ <br />
.k@B@B@B@B@B@u. B@B@B@B@B2 @B@B@B@B@B@@@B@B@@@B@@@B@B@B@B@B@B@ <br />
:Lur:F@@@B@B@B@B@@@B B@B@B@B@B@ @@@B@B@B@B@B@@@B@B@B@B@B@B@B@B@B@J <br />
.vBMi :,,rB@B@B@B@BO @@@B@B@@@5 :i@@@B@B@B@@@B@B@B@B@@@B@@@B@B@B@@: <br />
1r @B@B@B@B@B@ LB@B@B@B@Bq N@Bi rB@B@B@B@B@B@B@@@B@B@B@B@B@B@@@B@ <br />
,L. @@B@B@B@G Li .@B@B@B@B@B@@@B@. B@B@viv@B@B@B@B@B@B@B@B@B@B@B@@@B@B@B@ <br />
r, @@@@@B@@@B@: : B@B@B@B@B@B@@@B@ :B@ @S@B@B@B@B@B@B@@@B@B@B@@@B@B@B@ <br />
.j iB@B@B@B@B@B@Bi:; G @@BrLk . i M@B@B@@@B@@@ GB@B@B@B@B@B@B@B@B@@@B@B@B@: <br />
,: : BUB@B@B@B@@@@@N0r@B@B B@B@ : :@B@B@B@@@B @B@B@B@B@B@B@B@B@B@B@B@. B <br />
@i ui M .. @B@: B.@@B@BB:O @. B @ i B@@@@@B@ @@@B@B@B@@@B @@B@B@B@@@B @ @ <br />
E@ @7 ; .U i N@B@ B .@ @ @B@B@B@B@ v .GB @B@B@B@B@@@M: @B@B@B@B@BZ @ Y <br />
Bu .@ M 7v7 @ :B@B@@@B@BU @B@B@B@B@ BB@B@B@B@B@Bi B@@@B@B@B@v <br />
5@ @7 L S .q .N@B@@@B@BBB@B@B@B@B@B@kqqSB@B@B@B@B@ @B@B@B@B@@r <br />
q : B Z .: 2@B@B@B@..L. . M@B@B@:@BiP @B B@@@B@B@B@ <br />
; B@@@B@B. @@@B@: 2@B@B@ i @B@@@B@B@B <br />
7@B@k@B@B@B@B@B@B@B@ B@B@@@@@Bi <br />
jB@B@B@@@B@@O @B@B@B@B@ <br />
B@B@B@B@k B@@@B@@@B <br />
rPS: @B@ @B@B@ <br />
. :: ,ui:,: vL:,:: B@B B@B@B <br />
.B@B@B@B@i @B@@@B@5 @B@B :@@@@@ OB@@@ @B@ @B@@@ <br />
@B@B@ FB@B@ 7@B@B@ .@@@B @B@B iMB@B@S .GB@B@.,B@@L vG0Sqv;:@ L@@ ..E@B <br />
B@@@B@@@B@B@B .@B@@ :B@B@ B@B@ @B@B@..B@B@ @B@@@2.B@B@ @B@BBM S@1 S@. <br />
@B@B@B. ,@B@B8 B@B@ .@B@@ @B@@ B@B@8:iii;Mv i@B@M .rr B@B@B@B k@r EJ <br />
S@B@B@B@ EB@B@B@B. B@B@B@, r@B@B@1 @B@B@B: YB@@@r. 7@B@B@2 @@@@@@MB@B@ ,Bi <br />
:r, .i ,: .i: :i ,:.i. i,:: :: :J@B@X7 i. i: r :,:. ,@ a <br />
.B n <br />
[ P R E S E N T S ] @ t <br />
@ i <br />
0 / <br />
B 4 <br />
@ 0 <br />
. 4 <br />
<br />
Good.Behavior.S01E03.PROPER.720p.HDTV.x264-KILLERS<br />
<br />
<br />
Day: 2016-11-29<br />
Resolution: 1280x720<br />
Size: 1.02 GiB<br />
FrameRate: 23.976<br />
Length: 00:49:02.144<br />
Bitrate: 2 535 Kbps<br />
Note: FLEET is missing the last seg<br />
<br />
<br />
n***** We all miss you. Come back soon.]]>
</description>
<pubDate>Tue, 29 Nov 2016 10:55:58 +0000</pubDate>
<link>https://alpharatio.cc/torrents.php?action=download&amp;authkey=private_auth_key&amp;torrent_pass=private_torrent_pass&amp;id=465960</link>
<guid>https://alpharatio.cc/torrents.php?action=download&amp;authkey=private_auth_key&amp;torrent_pass=private_torrent_pass&amp;id=465960</guid>
<comments>https://alpharatio.cc/torrents.php?id=465989</comments>
<dc:creator>Anonymous</dc:creator>
</item>
<item>
<title><![CDATA[TvHD 465860 465831 WWE.RAW.2016.11.28.720p.HDTV.x264-KYR]]></title>
<description>
<![CDATA[&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&deg;&deg; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&deg; &szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&szlig;&deg;&deg;&Uuml;&deg;&Ucirc;&Ucirc;&sup2;&szlig;&Uuml;&Ucirc;&Ucirc;&Uuml;&Uuml;&sup2; &Uuml;&szlig;&sup2; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig; &Uuml;&szlig;&deg;&sup2;&deg;&sup2;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&plusmn;&sup2;&sup2; &Uuml;&Yacute; &THORN; &THORN;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&deg; &Ucirc;&Ucirc;&Yacute;&sup2;&THORN;&sup2;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&plusmn;&sup2;&Yacute;&Yacute;&THORN;&Ucirc; &szlig; &Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&sup2;&Ucirc;&Ucirc;&deg;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Yacute;&plusmn;&Ucirc;&Ucirc;&Ucirc;&sup2;&Ucirc;&Ucirc;&deg;&Ucirc;&Ucirc;&THORN;&sup2;&THORN; &Yacute; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&szlig; &szlig;&szlig;&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&deg;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&deg;&Ucirc;&sup2;&Ucirc;&THORN;&sup2; &Yacute;&sup2; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig; &szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&deg;&deg;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Yacute;&Ucirc;&Ucirc;&szlig;&Yacute;&sup2;&deg;&THORN;&THORN; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&deg;&Ucirc;&Yacute;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&sup2;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&THORN;&THORN;&THORN;&Uuml;&Ucirc;&Ucirc;&deg;&sup2;&Yacute;&THORN; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&sup2;&Yacute;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&deg;&Ucirc;&sup2;&THORN;&Ucirc;&Ucirc;&Ucirc; &THORN; &sup2; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &sup2;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Yacute;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Yacute;&Ucirc; &sup2; &Yacute; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute; &Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&sup2;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Yacute;&Ucirc;&THORN;&Ucirc;&Ucirc;&THORN;&sup2;&THORN; &Yacute;&Yacute; &Uuml;&Uuml;&Uuml;&Uuml; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Uuml;&Uuml; &Uuml; &THORN;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&THORN;&Ucirc;&Ucirc;&sup2;&THORN;&deg;&THORN; &sup2;&Yacute; &szlig;&szlig;&szlig;&Ucirc;&Ucirc;&Ucirc;&sup2; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &Ucirc;&Ucirc;&sup2;&sup2;&Ucirc;&Ucirc;&sup2; &THORN;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&THORN;&Ucirc;&Yacute;&Ucirc;&Ucirc;&plusmn;&THORN; &sup2; &Yacute;&THORN; &szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&THORN; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&plusmn; &Uuml;&Uuml; &szlig;&sup2;&Ucirc;&Ucirc;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&THORN;&Yacute;&Ucirc;&Yacute;&Ucirc;&Ucirc;&deg;&Ucirc; &sup2; &THORN; &sup2; &Uuml;&szlig;&Ucirc;&Ucirc;&Ucirc;&szlig; &THORN;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Yacute; &Ucirc;&Ucirc;&szlig; &Uuml;&Uuml;&Uuml;&szlig;&sup2; &Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&THORN;&THORN;&Ucirc;&Uuml;&sup2; &THORN; &Yacute; &Yacute; &szlig;&szlig;&szlig; &szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&szlig; &sup2;&szlig; &sup2;&THORN;&Yacute; &THORN;&Ucirc;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&Yacute;&Ucirc;&sup2;&THORN;&Ucirc;&Yacute;&Ucirc;&Yacute; &Yacute; &Yacute; &THORN; &szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig; &THORN; &THORN;&Yacute; &THORN;&Ucirc;<br />
&plusmn;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&THORN;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Yacute;&THORN;&Ucirc; &Yacute;&THORN; &szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Yacute; &sup2;&Ucirc;&Uuml; &Ucirc;&Ucirc;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&deg;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&deg;&Ucirc; &szlig; &THORN; &Yacute; &Yacute; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Uuml;&Ucirc;&Ucirc;&Uuml; &Yacute; &Uuml;&szlig; &szlig;&Ucirc;&Ucirc;<br />
&Ucirc;&sup2;&Ucirc;&Ucirc;&Yacute;&Yacute;&Yacute;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&sup2;&THORN; &Yacute; &sup2;&Uuml;&sup2;&Ucirc;&Ucirc; &Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&Ucirc;&szlig;&szlig;&szlig; &Uuml;&Uuml;&Uuml;&sup2;&szlig; &Ucirc;&Ucirc;<br />
&Ucirc;&Yacute;&Ucirc;&Ucirc;&THORN;&Yacute;&deg;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Yacute;&Ucirc;&sup2;&Yacute; &Yacute; &szlig;&szlig;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &deg; &szlig;&sup2;&szlig; &sup2; &THORN;&Ucirc;<br />
&Ucirc;&Ucirc;&deg;&Ucirc;&THORN;&Ucirc;&THORN;&THORN;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&deg;&Ucirc;&Ucirc;&sup2;&THORN;&Ucirc; &Yacute; &Yacute; &Uuml;&Uuml; &THORN;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Uuml;&Ucirc;&Uuml;&Uuml; &plusmn;&deg;&deg; &szlig; &Ucirc;<br />
&Ucirc;&Ucirc;&Ucirc;&THORN;&Yacute;&Ucirc;&THORN;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc; &sup2;&Yacute; &THORN; &THORN;&deg;&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig; &plusmn;&sup2;&plusmn;&plusmn; &THORN;<br />
&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Yacute;&Ucirc;&THORN;&Ucirc;&Ucirc;&Yacute;&sup2;&THORN;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Yacute;&THORN;&Yacute; &Yacute; &sup2;&szlig; &Uuml;&szlig;&Yacute;&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Uuml; &Uuml;&Uuml;&sup2;&Ucirc;&Ucirc;&Ucirc;&sup2;&sup2; &THORN;<br />
&Ucirc;&Ucirc;&Yacute;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&deg;&Ucirc;&sup2;&THORN;&Yacute;&Ucirc;&deg;&Ucirc;&THORN; &Yacute;&Yacute; &Uuml;&Uuml;&sup2;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&sup2;&Uuml; &Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&plusmn;<br />
&Ucirc;&szlig;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&THORN;&Ucirc;&sup2;&THORN;&Yacute;&Yacute;&Ucirc; &Yacute;&Yacute;&Yacute; &deg; &Yacute;&szlig;&Ucirc;&sup2;&Yacute;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig; &sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&szlig;&szlig;&szlig; &szlig;&Uuml;&sup2;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&THORN;&Ucirc;&Ucirc;&deg;&Ucirc;&Ucirc;&Ucirc; &sup2;&sup2;&plusmn;&plusmn;&THORN; &szlig;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Uuml;&szlig;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&sup2;&Yacute;&sup2;&Uuml; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&THORN;&Uuml; &Uuml;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute; &Uuml; &THORN;<br />
&szlig;&sup2;&Ucirc;&THORN;&sup2;&Ucirc;&Ucirc;&Ucirc; &Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&THORN;&THORN;&Ucirc;&Ucirc;&sup2;&Ucirc;&sup2;&THORN;&sup2;&Yacute; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&THORN;&Ucirc;&Ucirc;&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig; &THORN;&Ucirc;<br />
&Uuml;&sup2;&szlig;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&sup2;&Ucirc;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &Yacute;&Ucirc;<br />
&szlig; &Uuml;&Uuml;&szlig;&szlig;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Uuml;&Ucirc;&szlig;&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &THORN; &Ucirc;<br />
&szlig; &szlig;&Ucirc;&Ucirc;&Uuml;&Uuml;&Yacute;&Ucirc;&Ucirc;&Ucirc;&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Uuml;&Uuml;&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&Uuml;&Uuml;&szlig; &Yacute;&THORN;&sup2;<br />
&szlig;&Yacute;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&Uuml; &sup2; &Ucirc;&sup2;<br />
&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&THORN;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig; &sup2; &THORN;&sup2;&deg;<br />
&szlig; &sup2;&THORN;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&sup2; &sup2; &Uuml;&Ucirc;&sup2;<br />
&THORN;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&Ucirc; &sup2; &Uuml;&Ucirc;&sup2;&deg;<br />
&Uuml;&szlig; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&Yacute;&szlig; &Uuml;&szlig; &Uuml;&Ucirc;&sup2;&deg;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&Uuml;&sup2; &Uuml;&szlig; &Uuml;&Ucirc;&Ucirc;&sup2;&deg;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Ucirc;&sup2; &Uuml;&sup2;&szlig; &Uuml;&Ucirc;&Ucirc;&sup2;&plusmn;&deg;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Ucirc;&sup2; &Uuml;&Uuml;&szlig;&szlig; &Uuml;&Ucirc;&Ucirc;&sup2;&plusmn;&deg;<br />
&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Ucirc;&sup2; &Uuml;&Uuml;&szlig;&szlig; &Uuml;&Uuml;&Ucirc;&Ucirc;&Ucirc;&sup2;&plusmn;&deg;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Ucirc;&sup2; &Uuml;&Uuml;&szlig;&szlig; &Uuml;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&sup2;&plusmn;&deg;<br />
&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute;&Ucirc;&sup2;&Uuml;&Uuml;&szlig;&szlig; &Uuml;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&sup2;&sup2;&plusmn;&deg;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&szlig;&szlig;&szlig;&Uuml;&Uuml;&Uuml;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&sup2;&sup2;&plusmn;&plusmn;&deg;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&sup2;&sup2;&plusmn;&plusmn;&deg;<br />
&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &deg;&sup2;&szlig;&sup2;&sup2;&sup2;&sup2;&plusmn;&plusmn;&deg;<br />
&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &deg;&sup2;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute; &deg;&plusmn;&Yacute;<br />
&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Uuml;&deg;&deg;&plusmn;&sup2;<br />
&sup2;&Ucirc;&Ucirc;&Ucirc;&sup2;&szlig;&sup2;&sup2;&szlig;<br />
&Ucirc;&sup2;&Ucirc;<br />
&THORN;&sup2;&Yacute;<br />
&plusmn;<br />
&deg;<br />
<br />
&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;<br />
&Uuml;&szlig;&szlig;&Ucirc;&Ucirc;&szlig; &szlig;&Ucirc;&sup2; &Uuml;&szlig;&szlig;&Ucirc;&Ucirc;&szlig; &szlig;&Ucirc;&sup2; &Uuml;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig; &szlig;&Ucirc;&sup2;<br />
&Ucirc;&Ucirc;&Ucirc;&deg;&plusmn;&Ucirc;&Yacute; &sup2;&Ucirc;&Uuml; &Ucirc;&Ucirc;&Ucirc;&deg;&plusmn;&Ucirc;&Yacute; &Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&deg;&plusmn;&Ucirc;&Yacute;<br />
&THORN;&Ucirc;&Ucirc;&Ucirc;&sup2;&Ucirc;&Ucirc; &Ucirc;&Ucirc;&Yacute; &THORN;&Ucirc;&Ucirc;&Ucirc;&sup2;&Ucirc;&Yacute;&Ucirc;&Ucirc;&Ucirc;&szlig; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&Ucirc;&Ucirc;<br />
&Uuml;&Uuml; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &THORN;&Ucirc;&Ucirc;&Yacute; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &sup2;&Ucirc;&Yacute; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;<br />
&THORN;&Ucirc;&Ucirc; &sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Ucirc;&Ucirc;&Ucirc; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &THORN;&Ucirc;&Ucirc; &sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&Ucirc;&Ucirc;&Yacute; &sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &Ucirc;&Ucirc;&Ucirc; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute; &Ucirc;&Ucirc;&Yacute; &sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;<br />
&THORN;&Ucirc;&sup2; &Uuml;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Uuml;&Uuml;&Uuml; &THORN;&Ucirc;&Ucirc;&Yacute; &sup2;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &THORN;&Ucirc;&sup2; &Uuml;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Uuml;&Uuml;&Uuml; &szlig;<br />
&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&szlig;&Ucirc;&Uuml; &sup2;&Ucirc;&Ucirc;&Uuml;&Uuml;&Uuml;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&szlig;&szlig;&Ucirc;&Uuml;<br />
&THORN;&Ucirc;&Ucirc;&Ucirc;&szlig;&szlig; &szlig;&Ucirc;&Ucirc;&Ucirc;&deg;&THORN;&Ucirc; &szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &THORN;&Ucirc;&Ucirc;&Ucirc;&szlig;&szlig; &szlig;&Ucirc;&Ucirc;&Ucirc;&deg;&THORN;&Ucirc;<br />
&sup2;&Ucirc;&sup2; &Ucirc;&Ucirc;&Ucirc;&Uuml;&Ucirc;&Yacute; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Yacute; &sup2;&Ucirc;&Ucirc; &Ucirc;&Ucirc;&Ucirc;&Uuml;&Ucirc;&Yacute;<br />
&THORN;&Ucirc;&Ucirc;&Yacute; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2; &THORN;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &THORN;&Ucirc;&Ucirc;&Yacute; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;<br />
&Ucirc;&Ucirc;&Ucirc; &Ucirc;&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc; &Ucirc;&szlig;&Ucirc;&Ucirc;&Ucirc;&Yacute; &Ucirc;&Ucirc;&Ucirc; &Ucirc;&szlig;&Ucirc;&Ucirc;&Ucirc;&Ucirc;<br />
&szlig; &Ucirc;&deg;&THORN;&Ucirc;&Ucirc;&Ucirc;&Yacute; &Ucirc;&deg;&THORN;&Ucirc;&Ucirc;&Ucirc; &szlig; &Ucirc;&deg;&THORN;&Ucirc;&Ucirc;&Ucirc;&Yacute;<br />
&Ucirc;&plusmn;&deg;&Ucirc;&Ucirc;&Ucirc;&sup2; &Ucirc;&plusmn;&deg;&Ucirc;&Ucirc;&Ucirc;&sup2; &Ucirc;&plusmn;&deg;&Ucirc;&Ucirc;&Ucirc;&sup2;<br />
&THORN;&plusmn;&plusmn;&plusmn;&deg;&Ucirc;&Ucirc; &THORN;&plusmn;&plusmn;&plusmn;&deg;&Ucirc;&Ucirc; &THORN;&plusmn;&plusmn;&plusmn;&deg;&Ucirc;&Ucirc; presents..<br />
&THORN;&sup2;&plusmn;&plusmn;&plusmn;&plusmn;&Yacute; &THORN;&sup2;&plusmn;&plusmn;&plusmn;&plusmn;&Yacute; &THORN;&sup2;&plusmn;&plusmn;&plusmn;&plusmn;&Yacute;<br />
&Ucirc;&sup2;&sup2;&sup2;&Ucirc;&Uuml; &Uuml;&sup2; &Ucirc;&sup2;&sup2;&sup2;&Ucirc;&Uuml; &Uuml;&sup2; &Ucirc;&sup2;&sup2;&sup2;&Ucirc;&Uuml; &Uuml;&sup2;<br />
&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;<br />
k n o w y o u r r o l e<br />
<br />
&uacute; &uacute;&uacute;--&Auml;-&Auml;-&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;-&Auml;-&Auml;--&uacute;&uacute; &uacute;<br />
WWE.RAW.2016.11.28.720p.HDTV.h264-KYR<br />
&uacute; &uacute;&uacute;--&Auml;-&Auml;-&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;-&Auml;-&Auml;--&uacute;&uacute; &uacute;<br />
&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;<br />
&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&szlig;&Uuml;&deg;&Ucirc; &Ucirc;&sup2;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&deg;&Uuml;&szlig;&Ucirc; &Uuml;&Uuml;&Ucirc;&szlig;&Uuml;&deg;&Ucirc; &Ucirc;&Uuml;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&szlig;&Uuml;&Uuml;&Ucirc;&deg;&Uuml;&szlig;&Ucirc;<br />
&Uacute;&Auml;&Auml;&Ucirc; &szlig;&Uuml;&Ucirc; &Uuml;&Ucirc;&Ucirc; &Ucirc;&sup2;&Ucirc; &Uuml;&Ucirc;&Ucirc; &Uuml; &Ucirc;&Uuml;&Uuml;&deg;&Ucirc; &Uuml;&Ucirc;&szlig;&Auml;&Auml;&Ucirc; &Ucirc; &Ucirc; &Ucirc; &Uuml;&Ucirc;&Ucirc; &Ucirc; &Ucirc;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&uacute;&uacute; &uacute;<br />
&sup3; &Ucirc; &Ucirc; &Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&Ucirc; &Ucirc;&Uuml;&szlig; &Ucirc;&Uuml;&szlig;&szlig;&Ucirc; &Ucirc; &Ucirc;&Uuml;&Ucirc; &Ucirc;&deg;&Ucirc;&sup2;&Ucirc; &szlig; &Ucirc;<br />
&sup3; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;<br />
&sup3;<br />
&sup3; title&uacute;[ WWE RAW ]&uacute;<br />
&sup3; genre&uacute;[ Wrestling ]&uacute; crf&uacute;[ 23 ]&uacute;<br />
&sup3; rel. date&uacute;[ 11.28.16 ]&uacute; format&uacute;[ x264 ]&uacute;<br />
&sup3; air date&uacute;[ 11.28.16 ]&uacute; source&uacute;[ HDTV ]&uacute;<br />
&sup3; runtime&uacute;[ 2h 13m 48s ]&uacute; bitrate&uacute;[ 4111kbps ]&uacute;<br />
&sup3; filesize&uacute;[ 4.28 GB ]&uacute; resolu.&uacute;[ 1280x720 ]&uacute;<br />
&sup3; rar count&uacute;[ 93x50mb ]&uacute; frames&uacute;[ 59.940 ]&uacute;<br />
&sup3; &uacute;[ audio&uacute;[ 384 kbps AC3 5.1 ]&uacute;<br />
&sup3; &uacute;[ location&uacute;[ USA ]&uacute;<br />
&sup3; &uacute;[ ]&uacute;<br />
&sup3; url &uacute;[ http://www.wwe.com ]&uacute; <br />
&sup3;<br />
&sup3;<br />
&sup3; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;<br />
&sup3; &Ucirc;&szlig;&Uuml;&deg;&Ucirc;&szlig;&Uuml;&deg;&Ucirc; &Ucirc;&sup2;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&deg;&Uuml;&szlig;&Ucirc; &Uuml;&Uuml;&Ucirc;&szlig;&Uuml;&deg;&Ucirc; &Ucirc;&szlig;&Uuml;&deg;&Ucirc;&deg;&Uuml;&szlig;&Ucirc;&Uuml; &Uuml;&Ucirc;&szlig;&Uuml;&deg;&Ucirc; &Uuml;&Uuml;&Ucirc;<br />
&sup3; &uacute; &uacute;&uacute;&Auml;&Auml;&Auml;&Auml;&Auml;-&Ucirc; &szlig;&Uuml;&Ucirc; &Uuml;&Ucirc;&Ucirc; &Ucirc;&sup2;&Ucirc; &Uuml;&Ucirc;&Ucirc; &Uuml; &Ucirc;&Uuml;&Uuml;&deg;&Ucirc; &Uuml;&Ucirc;&szlig;&Auml;&Auml;&Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc;&Ucirc; &Ucirc;&Ucirc; &Uuml;&Ucirc;&Ucirc;&Uuml;&Uuml;&deg;&Ucirc;&Auml;&Auml;&acute;<br />
&sup3; &Ucirc; &Ucirc; &Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&Ucirc; &Ucirc;&Uuml;&szlig; &Ucirc;&Uuml;&szlig;&szlig;&Ucirc; &Ucirc; &Ucirc; &Ucirc; &szlig; &Ucirc;&Ucirc;&deg;&Ucirc;&Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&szlig; &Ucirc; &sup3;<br />
&sup3; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &sup3;<br />
&sup3; &sup3;<br />
&sup3; &sup3;<br />
&sup3; Enjoy! &sup3;<br />
&sup3; &sup3;<br />
&sup3; &sup3;<br />
&sup3; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &sup3;<br />
&sup3; &Ucirc;&szlig;&Uuml;&deg;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&deg;&Uuml;&szlig;&Ucirc;&szlig;&Ucirc;&deg;&Ucirc;&szlig;&Uuml;&deg;&Ucirc; &Ucirc;&Uuml;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&szlig;&Uuml;&Uuml;&Ucirc;&deg;&Uuml;&szlig;&Ucirc; &sup3;<br />
&Atilde;&Auml;&Auml;&Ucirc; &Yacute;&szlig;&Ucirc; &szlig;&Uuml;&Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc; &szlig; &Ucirc;&Auml;&Auml;&Ucirc;&deg;&Ucirc; &Ucirc; &Ucirc; &Uuml;&Ucirc;&Ucirc; &Ucirc; &Ucirc;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;-&Auml;&Auml;&Auml;&Auml;&Auml;&uacute;&uacute; &uacute; &sup3;<br />
&Ucirc; &szlig; &Ucirc; &Ucirc; &Ucirc; &szlig; &Ucirc; &szlig; &Ucirc; &Ucirc;&szlig;&szlig; &Ucirc; &Ucirc; &Ucirc; &Ucirc;&deg;&Ucirc;&sup2;&Ucirc; &szlig; &Ucirc; &sup3;<br />
&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig; &sup3;<br />
&sup3;<br />
 group info &sup3;<br />
&sup3;<br />
Know Your Role and Shut Your Mouth! &sup3;<br />
&sup3;<br />
 we are now looking for... &sup3;<br />
&sup3;<br />
(a) capper(s) of cable, PPV, good upspeed advantageous &sup3;<br />
.. contact in the usual way. &sup3;<br />
&sup3;<br />
 KYR respects... &sup3;<br />
&sup3;<br />
everyone keeping it real and oldschool. we love ya! &sup3;<br />
&sup3;<br />
&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &sup3;<br />
&Uuml;&Uuml;&Uuml;&Uuml;&sup2; &Uuml;&Uuml;&Uuml; &Ucirc;&Uuml;&sup2; &Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &sup2;&Yacute; &sup3;<br />
&uacute; &uacute;&uacute;&Auml;&Auml;&Auml;&Auml;&Auml;--&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Ucirc; &Uuml;&Uuml; &Yacute;&THORN;&Ucirc;&Ucirc;&Yacute;&Uuml;&Uuml; &Yacute;&THORN;&Ucirc;&Ucirc;&Yacute;&szlig;&szlig; &THORN;&Ucirc;&Ucirc;&Yacute;&THORN;&Ucirc; &Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&acute;<br />
&Ucirc; &Ucirc;&Ucirc; &Uuml;&Ucirc;&Ucirc;&szlig; &Ucirc;&Ucirc; &Uuml;&Ucirc;&Ucirc;&sup2; &Ucirc;&Ucirc; &Uuml;&Ucirc;&Ucirc;&szlig; &Ucirc;&Yacute; K N O W &sup3;<br />
ascii crafted by &Ucirc; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&Uuml; &THORN;&Ucirc;&Ucirc;&sup2;&szlig; &Uuml; &Ucirc;&Ucirc;&Ucirc;&Ucirc;&sup2;&Uuml; &szlig;&Ucirc; &sup3;<br />
&Ucirc; &Ucirc;&Ucirc; &szlig;&Ucirc;&Ucirc;&sup2; &Ucirc;&Ucirc; &Uuml;&sup2;&Ucirc; &Ucirc;&Ucirc; &szlig;&Ucirc;&Ucirc;&sup2; &sup2;&Yacute; Y O U R &sup3;<br />
h8`!HiGHONASCii &Ucirc; &Ucirc;&Ucirc; &Yacute;&THORN;&Ucirc;&Ucirc;&Yacute; &Ucirc;&Ucirc; &Ucirc; &Ucirc; &Ucirc;&Ucirc; &Yacute;&THORN;&Ucirc;&Ucirc;&Yacute;&THORN;&Ucirc; &sup3;<br />
&Ucirc; &Ucirc;&sup2; &Ucirc; &Ucirc;&Ucirc;&sup2; &Ucirc;&sup2; &Ucirc; &Ucirc; &Ucirc;&sup2; &Ucirc; &Ucirc;&Ucirc;&sup2; &Ucirc; R O L E &sup3;<br />
&uacute; &uacute;&uacute;&Auml;-&Auml;----&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Ucirc;&Uuml;&Uuml;&Uuml;&Uuml;&sup2;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&sup2; &Ucirc;&Uuml;&Uuml;&Uuml;&Uuml;&sup2;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&sup2; &Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Auml;&Ugrave;<br />
&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;<br />
&deg;&plusmn;&sup2;&Ucirc; &Uuml;&szlig;&Ucirc; &Uuml;&szlig;&Ucirc; &Uuml;&szlig;&Ucirc; &Uuml;&szlig;&Ucirc;&sup2;&sup2;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&szlig;&Uuml;&deg;&Ucirc; &Ucirc;&sup2;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&deg;&Uuml;&szlig;&Ucirc; &Uuml;&Uuml;&Ucirc;&szlig;&Uuml;&deg;&Ucirc; &Uuml;&Uuml;&Ucirc;&sup2;&sup2;&Ucirc;&deg;&Uuml;&szlig;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&deg;&Uuml;&szlig;&Ucirc;&sup2;&plusmn;&deg;<br />
&deg; &deg;&plusmn;&Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc;+&plusmn;&Ucirc; &szlig;&Uuml;&Ucirc; &Uuml;&Ucirc;&Ucirc; &Ucirc;&plusmn;&Ucirc; &Uuml;&Ucirc;&Ucirc; &Uuml; &Ucirc;&Uuml;&Uuml;&deg;&Ucirc; &Uuml;&Ucirc;&Ucirc;&Uuml;&Uuml;&deg;&Ucirc;&plusmn;&plusmn;&Ucirc; &Uuml; &Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc;&plusmn;&deg; &deg;<br />
&deg;&plusmn;&sup2;&Ucirc;&Uuml;&szlig;&deg;&Ucirc;&Uuml;&szlig;&deg;&Ucirc;&Uuml;&szlig;&deg;&Ucirc;&Uuml;&szlig;&deg;&Ucirc;&sup2;&sup2;&Ucirc; &Ucirc; &Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&Ucirc; &Ucirc;&Uuml;&szlig; &Ucirc;&Uuml;&szlig;&szlig;&Ucirc;&Uuml;&szlig; &Ucirc;&sup2;&sup2;&Ucirc;&Uuml;&Ucirc; &Ucirc;&Uuml;&Ucirc; &Ucirc; &szlig;&Uuml;&Ucirc;&sup2;&plusmn;&deg;<br />
&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;<br />
&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml; &Uuml;&Uuml; &Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;&Uuml;<br />
&deg;&plusmn;&sup2;&sup2;&Ucirc;&szlig;&Uuml; &Ucirc;&deg;&Uuml;&szlig;&Ucirc;&szlig;&Ucirc;&deg;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&Uuml; &Uuml;&Ucirc;&Uuml;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&szlig;&Uuml;&deg;&Ucirc;&deg;&Ucirc;&deg;&Ucirc;&deg;&Ucirc;&sup2;&sup2;&plusmn;&deg;<br />
&deg; &deg;&plusmn;&plusmn;&Ucirc; &Ucirc;&Ucirc;&Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc; &Ucirc;&Ucirc; &Ucirc;&Ucirc;&deg;&Ucirc; &Ucirc; &Ucirc; &Yacute;&szlig;&Ucirc; &Ucirc; &Ucirc; &Ucirc;&plusmn;&plusmn;&deg; &deg;<br />
&deg;&plusmn;&sup2;&sup2;&Ucirc;&Uuml;&szlig;&deg;&Ucirc; &szlig; &Ucirc; &szlig; &Ucirc;&Uuml;&Ucirc; &Ucirc;&Ucirc;&deg;&Ucirc;&Ucirc; &Ucirc; &Ucirc; &Ucirc;&deg;&szlig; &Ucirc;&szlig;&Ucirc;&szlig;&Ucirc;&szlig;&Ucirc;&sup2;&sup2;&plusmn;&deg;<br />
&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig; &szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;&szlig;]]>
</description>
<pubDate>Tue, 29 Nov 2016 05:08:18 +0000</pubDate>
<link>https://alpharatio.cc/torrents.php?action=download&amp;authkey=private_auth_key&amp;torrent_pass=private_torrent_pass&amp;id=465831</link>
<guid>https://alpharatio.cc/torrents.php?action=download&amp;authkey=private_auth_key&amp;torrent_pass=private_torrent_pass&amp;id=465831</guid>
<comments>https://alpharatio.cc/torrents.php?id=465860</comments>
<dc:creator>Anonymous</dc:creator>
</item>
</channel>
</rss>
@@ -1,7 +0,0 @@
{
"result": "failure",
"data": [ ],
"message": "no show with the tvdb_id 79488 found"
}
-10
View File
@@ -1,10 +0,0 @@
{
"result": "success",
"data": [
"73141",
"79886",
],
"message": ""
}
@@ -1,32 +0,0 @@
{
"result": "success",
"data": [
{
"scene": {
"season": 1,
"episode": 1,
"absolute": 1
},
"tvdb": {
"season": 1,
"episode": 1,
"absolute": 1
}
},
{
"scene": {
"season": 1,
"episode": 2,
"absolute": 2
},
"tvdb": {
"season": 1,
"episode": 2,
"absolute": 2
}
}
],
"message": "full mapping for 73388 on tvdb. this was a cached version"
}
@@ -1,24 +0,0 @@
{
"result": "success",
"data": {
"220571": [
"Is This a Zombie? Of the Dead",
"Kore wa Zombie Desuka?",
"Kore wa Zombie Desuka? Of the Dead",
"Kore wa Zombie Desuka Of the Dead",
"Kore wa Zombie Desu ka - Of the Dead",
"Kore wa Zombie Desu ka of the Dead"
],
"79151": [
"Fate Stay Night",
"Fate/Zero",
"Fate Zero",
"Fate/Zero (2012)",
"Fate Zero S2",
"Fate Zero"
]
},
"message": ""
}
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.HealthCheck; using NzbDrone.Core.HealthCheck;
namespace NzbDrone.Core.Test.HealthCheck.Checks namespace NzbDrone.Core.Test.HealthCheck.Checks
@@ -10,14 +11,24 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
result.Type.Should().Be(HealthCheckResult.Ok); result.Type.Should().Be(HealthCheckResult.Ok);
} }
public static void ShouldBeWarning(this Core.HealthCheck.HealthCheck result) public static void ShouldBeWarning(this Core.HealthCheck.HealthCheck result, string message = null)
{ {
result.Type.Should().Be(HealthCheckResult.Warning); result.Type.Should().Be(HealthCheckResult.Warning);
if (message.IsNotNullOrWhiteSpace())
{
result.Message.Should().Contain(message);
}
} }
public static void ShouldBeError(this Core.HealthCheck.HealthCheck result) public static void ShouldBeError(this Core.HealthCheck.HealthCheck result, string message = null)
{ {
result.Type.Should().Be(HealthCheckResult.Error); result.Type.Should().Be(HealthCheckResult.Error);
if (message.IsNotNullOrWhiteSpace())
{
result.Message.Should().Contain(message);
}
} }
} }
} }
@@ -0,0 +1,92 @@
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class IndexerRssCheckFixture : CoreTest<IndexerRssCheck>
{
private Mock<IIndexer> _indexerMock;
[SetUp]
public void SetUp()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer>());
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.RssEnabled(It.IsAny<bool>()))
.Returns(new List<IIndexer>());
}
private void GivenIndexer(bool supportsRss, bool supportsSearch)
{
_indexerMock = Mocker.GetMock<IIndexer>();
_indexerMock.SetupGet(s => s.SupportsRss).Returns(supportsRss);
_indexerMock.SetupGet(s => s.SupportsSearch).Returns(supportsSearch);
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer> { _indexerMock.Object });
}
private void GivenRssEnabled()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.RssEnabled(It.IsAny<bool>()))
.Returns(new List<IIndexer> { _indexerMock.Object });
}
private void GivenRssFiltered()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.RssEnabled(false))
.Returns(new List<IIndexer> { _indexerMock.Object });
}
[Test]
public void should_return_error_when_no_indexer_present()
{
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_error_when_no_rss_supported_indexer_present()
{
GivenIndexer(false, true);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_ok_when_rss_is_enabled()
{
GivenIndexer(true, false);
GivenRssEnabled();
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_error_if_rss_is_supported_but_disabled()
{
GivenIndexer(true, false);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_filter_warning_if_rss_is_enabled_but_filtered()
{
GivenIndexer(true, false);
GivenRssFiltered();
Subject.Check().ShouldBeWarning("recent indexer errors");
}
}
}
@@ -8,10 +8,22 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks namespace NzbDrone.Core.Test.HealthCheck.Checks
{ {
[TestFixture] [TestFixture]
public class IndexerCheckFixture : CoreTest<IndexerCheck> public class IndexerSearchCheckFixture : CoreTest<IndexerSearchCheck>
{ {
private Mock<IIndexer> _indexerMock; private Mock<IIndexer> _indexerMock;
[SetUp]
public void SetUp()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer>());
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.SearchEnabled(It.IsAny<bool>()))
.Returns(new List<IIndexer>());
}
private void GivenIndexer(bool supportsRss, bool supportsSearch) private void GivenIndexer(bool supportsRss, bool supportsSearch)
{ {
_indexerMock = Mocker.GetMock<IIndexer>(); _indexerMock = Mocker.GetMock<IIndexer>();
@@ -21,42 +33,30 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Mocker.GetMock<IIndexerFactory>() Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.GetAvailableProviders()) .Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer> { _indexerMock.Object }); .Returns(new List<IIndexer> { _indexerMock.Object });
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.RssEnabled())
.Returns(new List<IIndexer>());
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.SearchEnabled())
.Returns(new List<IIndexer>());
}
private void GivenRssEnabled()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.RssEnabled())
.Returns(new List<IIndexer> { _indexerMock.Object });
} }
private void GivenSearchEnabled() private void GivenSearchEnabled()
{ {
Mocker.GetMock<IIndexerFactory>() Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.SearchEnabled()) .Setup(s => s.SearchEnabled(It.IsAny<bool>()))
.Returns(new List<IIndexer> { _indexerMock.Object });
}
private void GivenSearchFiltered()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.SearchEnabled(false))
.Returns(new List<IIndexer> { _indexerMock.Object }); .Returns(new List<IIndexer> { _indexerMock.Object });
} }
[Test] [Test]
public void should_return_error_when_not_indexers_are_enabled() public void should_return_warning_when_no_indexer_present()
{ {
Mocker.GetMock<IIndexerFactory>() Subject.Check().ShouldBeWarning();
.Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer>());
Subject.Check().ShouldBeError();
} }
[Test] [Test]
public void should_return_warning_when_only_enabled_indexer_doesnt_support_search() public void should_return_warning_when_no_search_supported_indexer_present()
{ {
GivenIndexer(true, false); GivenIndexer(true, false);
@@ -64,7 +64,16 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
} }
[Test] [Test]
public void should_return_warning_when_only_enabled_indexer_doesnt_support_rss() public void should_return_ok_when_search_is_enabled()
{
GivenIndexer(false, true);
GivenSearchEnabled();
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_warning_if_search_is_supported_but_disabled()
{ {
GivenIndexer(false, true); GivenIndexer(false, true);
@@ -72,52 +81,12 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
} }
[Test] [Test]
public void should_return_ok_when_multiple_indexers_are_enabled() public void should_return_filter_warning_if_search_is_enabled_but_filtered()
{ {
GivenRssEnabled(); GivenIndexer(false, true);
GivenSearchEnabled(); GivenSearchFiltered();
var indexer1 = Mocker.GetMock<IIndexer>(); Subject.Check().ShouldBeWarning("recent indexer errors");
indexer1.SetupGet(s => s.SupportsRss).Returns(true);
indexer1.SetupGet(s => s.SupportsSearch).Returns(true);
var indexer2 = new Moq.Mock<IIndexer>();
indexer2.SetupGet(s => s.SupportsRss).Returns(true);
indexer2.SetupGet(s => s.SupportsSearch).Returns(false);
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer> { indexer1.Object, indexer2.Object });
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_ok_when_indexer_supports_rss_and_search()
{
GivenIndexer(true, true);
GivenRssEnabled();
GivenSearchEnabled();
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_warning_if_rss_is_supported_but_disabled()
{
GivenIndexer(true, true);
GivenSearchEnabled();
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_warning_if_search_is_supported_but_disabled()
{
GivenIndexer(true, true);
GivenRssEnabled();
Subject.Check().ShouldBeWarning();
} }
} }
} }
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
_mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true); _mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true);
Mocker.GetMock<IIndexerFactory>() Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.SearchEnabled()) .Setup(s => s.SearchEnabled(true))
.Returns(new List<IIndexer> { _mockIndexer.Object }); .Returns(new List<IIndexer> { _mockIndexer.Object });
Mocker.GetMock<IMakeDownloadDecision>() Mocker.GetMock<IMakeDownloadDecision>()
@@ -5,7 +5,6 @@ using NUnit.Framework;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Indexers.Omgwtfnzbs; using NzbDrone.Core.Indexers.Omgwtfnzbs;
using NzbDrone.Core.Indexers.Wombles;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -22,7 +21,6 @@ namespace NzbDrone.Core.Test.IndexerTests
_indexers.Add(Mocker.Resolve<Newznab>()); _indexers.Add(Mocker.Resolve<Newznab>());
_indexers.Add(Mocker.Resolve<Omgwtfnzbs>()); _indexers.Add(Mocker.Resolve<Omgwtfnzbs>());
_indexers.Add(Mocker.Resolve<Wombles>());
Mocker.SetConstant<IEnumerable<IIndexer>>(_indexers); Mocker.SetConstant<IEnumerable<IIndexer>>(_indexers);
} }
@@ -4,9 +4,7 @@ using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.KickassTorrents;
using NzbDrone.Core.Indexers.Nyaa; using NzbDrone.Core.Indexers.Nyaa;
using NzbDrone.Core.Indexers.Wombles;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -40,58 +38,6 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
}; };
} }
[Test]
public void wombles_fetch_recent()
{
var indexer = Mocker.Resolve<Wombles>();
indexer.Definition = new IndexerDefinition
{
Name = "MyIndexer",
Settings = NullConfig.Instance
};
var result = indexer.FetchRecent();
ValidateResult(result);
}
[Test]
[ManualTest]
[Explicit]
public void kickass_fetch_recent()
{
var indexer = Mocker.Resolve<KickassTorrents>();
indexer.Definition = new IndexerDefinition
{
Name = "MyIndexer",
Settings = new KickassTorrentsSettings()
};
var result = indexer.FetchRecent();
ValidateTorrentResult(result, hasSize: true);
}
[Test]
[ManualTest]
[Explicit]
public void kickass_search_single()
{
var indexer = Mocker.Resolve<KickassTorrents>();
indexer.Definition = new IndexerDefinition
{
Name = "MyIndexer",
Settings = new KickassTorrentsSettings()
};
var result = indexer.Fetch(_singleSearchCriteria);
ValidateTorrentResult(result, hasSize: true, hasMagnet: true);
}
[Test] [Test]
public void nyaa_fetch_recent() public void nyaa_fetch_recent()
{ {
@@ -1,173 +0,0 @@
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.KickassTorrents;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
using System;
using System.Linq;
using FluentAssertions;
using System.Text.RegularExpressions;
namespace NzbDrone.Core.Test.IndexerTests.KickassTorrentsTests
{
[TestFixture]
public class KickassTorrentsFixture : CoreTest<KickassTorrents>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "Kickass Torrents",
Settings = new KickassTorrentsSettings() { VerifiedOnly = false }
};
}
[Test]
public void should_parse_recent_feed_from_KickassTorrents()
{
var recentFeed = ReadAllText(@"Files/Indexers/KickassTorrents/KickassTorrents.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = (TorrentInfo) releases.First();
torrentInfo.Title.Should().Be("Doctor Stranger.E03.140512.HDTV.H264.720p-iPOP.avi [CTRG]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://torcache.net/torrent/208C4F7866612CC88BFEBC7C496FA72C2368D1C0.torrent?title=%5Bkickass.to%5Ddoctor.stranger.e03.140512.hdtv.h264.720p.ipop.avi.ctrg");
torrentInfo.InfoUrl.Should().Be("http://kickass.to/doctor-stranger-e03-140512-hdtv-h264-720p-ipop-avi-ctrg-t9100648.html");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2014/05/12 16:16:49"));
torrentInfo.Size.Should().Be(1205364736);
torrentInfo.InfoHash.Should().Be("208C4F7866612CC88BFEBC7C496FA72C2368D1C0");
torrentInfo.MagnetUrl.Should().Be("magnet:?xt=urn:btih:208C4F7866612CC88BFEBC7C496FA72C2368D1C0&dn=doctor+stranger+e03+140512+hdtv+h264+720p+ipop+avi+ctrg&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce");
}
[Test]
public void should_return_empty_list_on_404()
{
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0], System.Net.HttpStatusCode.NotFound));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(0);
ExceptionVerification.IgnoreWarns();
}
[Test]
public void should_not_return_unverified_releases_if_not_configured()
{
((KickassTorrentsSettings) Subject.Definition.Settings).VerifiedOnly = true;
var recentFeed = ReadAllText(@"Files/Indexers/KickassTorrents/KickassTorrents.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(4);
}
[Test]
public void should_set_seeders_to_null()
{
// Atm, Kickass supplies 0 as seeders and leechers on the rss feed (but not the site), so set it to null if there aren't any peers.
var recentFeed = ReadAllText(@"Files/Indexers/KickassTorrents/KickassTorrents.xml");
recentFeed = recentFeed.Replace("<pubDate>Mon, 12 May 2014 16:16:49 +0000</pubDate>", string.Format("<pubDate>{0:R}</pubDate>", DateTime.UtcNow));
recentFeed = Regex.Replace(recentFeed, @"(seeds|peers)\>\d*", "$1>0");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = (TorrentInfo)releases.First();
torrentInfo.Peers.Should().NotHaveValue();
torrentInfo.Seeders.Should().NotHaveValue();
}
[Test]
public void should_not_set_seeders_to_null_if_has_peers()
{
// Atm, Kickass supplies 0 as seeders and leechers on the rss feed (but not the site), so set it to null if there aren't any peers.
var recentFeed = ReadAllText(@"Files/Indexers/KickassTorrents/KickassTorrents.xml");
recentFeed = recentFeed.Replace("<pubDate>Mon, 12 May 2014 16:16:49 +0000</pubDate>", string.Format("<pubDate>{0:R}</pubDate>", DateTime.UtcNow));
recentFeed = Regex.Replace(recentFeed, @"(seeds)\>\d*", "$1>0");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = (TorrentInfo)releases.First();
torrentInfo.Peers.Should().Be(311);
torrentInfo.Seeders.Should().Be(0);
}
[Test]
public void should_not_set_seeders_to_null_if_older_than_12_hours()
{
// Atm, Kickass supplies 0 as seeders and leechers on the rss feed (but not the site), so set it to null if there aren't any peers.
var recentFeed = ReadAllText(@"Files/Indexers/KickassTorrents/KickassTorrents.xml");
recentFeed = Regex.Replace(recentFeed, @"(seeds|peers)\>\d*", "$1>0");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = (TorrentInfo)releases.First();
torrentInfo.Peers.Should().Be(0);
torrentInfo.Seeders.Should().Be(0);
}
[Test]
public void should_handle_xml_with_html_accents()
{
var recentFeed = ReadAllText(@"Files/Indexers/KickassTorrents/KickassTorrents_accents.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(5);
}
}
}
@@ -215,5 +215,22 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent); torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("http://storage.animetosho.org/torrents/4b58360143d59a55cbd922397a3eaa378165f3ff/DAYS%20-%2005%20%281280x720%20HEVC2%20AAC%29.torrent"); torrentInfo.DownloadUrl.Should().Be("http://storage.animetosho.org/torrents/4b58360143d59a55cbd922397a3eaa378165f3ff/DAYS%20-%2005%20%281280x720%20HEVC2%20AAC%29.torrent");
} }
[Test]
public void should_parse_recent_feed_from_AlphaRatio()
{
GivenRecentFeedResponse("TorrentRss/AlphaRatio.xml");
var releases = Subject.FetchRecent();
releases.Should().HaveCount(2);
releases.Last().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.Last() as TorrentInfo;
torrentInfo.Title.Should().Be("TvHD 465860 465831 WWE.RAW.2016.11.28.720p.HDTV.x264-KYR");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://alpharatio.cc/torrents.php?action=download&authkey=private_auth_key&torrent_pass=private_torrent_pass&id=465831");
}
} }
} }
@@ -180,6 +180,26 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
}); });
} }
[Test]
public void should_detect_rss_settings_for_AlphaRatio()
{
_indexerSettings.AllowZeroSize = true;
GivenRecentFeedResponse("TorrentRss/AlphaRatio.xml");
var settings = Subject.Detect(_indexerSettings);
settings.ShouldBeEquivalentTo(new TorrentRssIndexerParserSettings
{
UseEZTVFormat = false,
UseEnclosureUrl = false,
UseEnclosureLength = false,
ParseSizeInDescription = true,
ParseSeedersInDescription = false,
SizeElementName = null
});
}
[Test] [Test]
[Ignore("Cannot reliably reject unparseable titles")] [Ignore("Cannot reliably reject unparseable titles")]
public void should_reject_rss_settings_for_AwesomeHD() public void should_reject_rss_settings_for_AwesomeHD()
@@ -1,59 +0,0 @@
using System;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Wombles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Test.IndexerTests.WomblesTests
{
[TestFixture]
public class TorrentRssIndexerFixture : CoreTest<Wombles>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "Wombles",
Settings = new NullConfig(),
};
}
private void GivenRecentFeedResponse(string rssXmlFile)
{
var recentFeed = ReadAllText(@"Files/Indexers/" + rssXmlFile);
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
}
[Test]
public void should_parse_recent_feed_from_wombles()
{
GivenRecentFeedResponse("Wombles/wombles.xml");
var releases = Subject.FetchRecent();
releases.Should().HaveCount(5);
var releaseInfo = releases.First();
releaseInfo.Title.Should().Be("One.Child.S01E01.720p.HDTV.x264-TLA");
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet);
releaseInfo.DownloadUrl.Should().Be("http://indexer.local/nzb/bb4/One.Child.S01E01.720p.HDTV.x264-TLA.nzb");
releaseInfo.InfoUrl.Should().BeNullOrEmpty();
releaseInfo.CommentUrl.Should().BeNullOrEmpty();
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
releaseInfo.PublishDate.Should().Be(DateTime.Parse("2016-02-17 23:03:52 +0000").ToUniversalTime());
releaseInfo.Size.Should().Be(956*1024*1024);
}
}
}
@@ -16,12 +16,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
[TestFixture] [TestFixture]
public class UpdateMediaInfoServiceFixture : CoreTest<UpdateMediaInfoService> public class UpdateMediaInfoServiceFixture : CoreTest<UpdateMediaInfoService>
{ {
private Series _series; private Movie _series;
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
_series = new Series _series = new Movie
{ {
Id = 1, Id = 1,
Path = @"C:\series".AsOsAgnostic() Path = @"C:\series".AsOsAgnostic()
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
[Test] [Test]
public void should_skip_up_to_date_media_info() public void should_skip_up_to_date_media_info()
{ {
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(3) var episodeFiles = Builder<MovieFile>.CreateListOfSize(3)
.All() .All()
.With(v => v.RelativePath = "media.mkv") .With(v => v.RelativePath = "media.mkv")
.TheFirst(1) .TheFirst(1)
@@ -64,25 +64,25 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
.BuildList(); .BuildList();
Mocker.GetMock<IMediaFileService>() Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesBySeries(1)) .Setup(v => v.GetFilesByMovie(1))
.Returns(episodeFiles); .Returns(episodeFiles);
GivenFileExists(); GivenFileExists();
GivenSuccessfulScan(); GivenSuccessfulScan();
Subject.Handle(new SeriesScannedEvent(_series)); Subject.Handle(new MovieScannedEvent(_series));
Mocker.GetMock<IVideoFileInfoReader>() Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(2)); .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(2));
Mocker.GetMock<IMediaFileService>() Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(2)); .Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Exactly(2));
} }
[Test] [Test]
public void should_update_outdated_media_info() public void should_update_outdated_media_info()
{ {
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(3) var episodeFiles = Builder<MovieFile>.CreateListOfSize(3)
.All() .All()
.With(v => v.RelativePath = "media.mkv") .With(v => v.RelativePath = "media.mkv")
.TheFirst(1) .TheFirst(1)
@@ -90,48 +90,48 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
.BuildList(); .BuildList();
Mocker.GetMock<IMediaFileService>() Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesBySeries(1)) .Setup(v => v.GetFilesByMovie(1))
.Returns(episodeFiles); .Returns(episodeFiles);
GivenFileExists(); GivenFileExists();
GivenSuccessfulScan(); GivenSuccessfulScan();
Subject.Handle(new SeriesScannedEvent(_series)); Subject.Handle(new MovieScannedEvent(_series));
Mocker.GetMock<IVideoFileInfoReader>() Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(3)); .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(3));
Mocker.GetMock<IMediaFileService>() Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(3)); .Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Exactly(3));
} }
[Test] [Test]
public void should_ignore_missing_files() public void should_ignore_missing_files()
{ {
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(2) var episodeFiles = Builder<MovieFile>.CreateListOfSize(2)
.All() .All()
.With(v => v.RelativePath = "media.mkv") .With(v => v.RelativePath = "media.mkv")
.BuildList(); .BuildList();
Mocker.GetMock<IMediaFileService>() Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesBySeries(1)) .Setup(v => v.GetFilesByMovie(1))
.Returns(episodeFiles); .Returns(episodeFiles);
GivenSuccessfulScan(); GivenSuccessfulScan();
Subject.Handle(new SeriesScannedEvent(_series)); Subject.Handle(new MovieScannedEvent(_series));
Mocker.GetMock<IVideoFileInfoReader>() Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo("media.mkv"), Times.Never()); .Verify(v => v.GetMediaInfo("media.mkv"), Times.Never());
Mocker.GetMock<IMediaFileService>() Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Never()); .Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Never());
} }
[Test] [Test]
public void should_continue_after_failure() public void should_continue_after_failure()
{ {
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(2) var episodeFiles = Builder<MovieFile>.CreateListOfSize(2)
.All() .All()
.With(v => v.RelativePath = "media.mkv") .With(v => v.RelativePath = "media.mkv")
.TheFirst(1) .TheFirst(1)
@@ -139,20 +139,20 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
.BuildList(); .BuildList();
Mocker.GetMock<IMediaFileService>() Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesBySeries(1)) .Setup(v => v.GetFilesByMovie(1))
.Returns(episodeFiles); .Returns(episodeFiles);
GivenFileExists(); GivenFileExists();
GivenSuccessfulScan(); GivenSuccessfulScan();
GivenFailedScan(Path.Combine(_series.Path, "media2.mkv")); GivenFailedScan(Path.Combine(_series.Path, "media2.mkv"));
Subject.Handle(new SeriesScannedEvent(_series)); Subject.Handle(new MovieScannedEvent(_series));
Mocker.GetMock<IVideoFileInfoReader>() Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(1)); .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(1));
Mocker.GetMock<IMediaFileService>() Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(1)); .Verify(v => v.Update(It.IsAny<MovieFile>()), Times.Exactly(1));
} }
} }
} }
@@ -113,7 +113,6 @@
<Compile Include="Blacklisting\BlacklistServiceFixture.cs" /> <Compile Include="Blacklisting\BlacklistServiceFixture.cs" />
<Compile Include="Configuration\ConfigCachingFixture.cs" /> <Compile Include="Configuration\ConfigCachingFixture.cs" />
<Compile Include="Configuration\ConfigServiceFixture.cs" /> <Compile Include="Configuration\ConfigServiceFixture.cs" />
<Compile Include="DataAugmentation\SceneNumbering\XemServiceFixture.cs" />
<Compile Include="DataAugmentation\Scene\SceneMappingProxyFixture.cs" /> <Compile Include="DataAugmentation\Scene\SceneMappingProxyFixture.cs" />
<Compile Include="DataAugmentation\Scene\SceneMappingServiceFixture.cs" /> <Compile Include="DataAugmentation\Scene\SceneMappingServiceFixture.cs" />
<Compile Include="DataAugmentation\DailySeries\DailySeriesDataProxyFixture.cs" /> <Compile Include="DataAugmentation\DailySeries\DailySeriesDataProxyFixture.cs" />
@@ -183,6 +182,10 @@
<Compile Include="Download\DownloadClientTests\TransmissionTests\TransmissionFixtureBase.cs" /> <Compile Include="Download\DownloadClientTests\TransmissionTests\TransmissionFixtureBase.cs" />
<Compile Include="Download\DownloadClientTests\UTorrentTests\UTorrentFixture.cs" /> <Compile Include="Download\DownloadClientTests\UTorrentTests\UTorrentFixture.cs" />
<Compile Include="Download\DownloadClientTests\VuzeTests\VuzeFixture.cs" /> <Compile Include="Download\DownloadClientTests\VuzeTests\VuzeFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadStationTests\TorrentDownloadStationFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadStationTests\SerialNumberProviderFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadStationTests\SharedFolderResolverFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadStationTests\UsenetDownloadStationFixture.cs" />
<Compile Include="Download\DownloadServiceFixture.cs" /> <Compile Include="Download\DownloadServiceFixture.cs" />
<Compile Include="Download\FailedDownloadServiceFixture.cs" /> <Compile Include="Download\FailedDownloadServiceFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" />
@@ -211,9 +214,10 @@
<Compile Include="HealthCheck\Checks\AppDataLocationFixture.cs" /> <Compile Include="HealthCheck\Checks\AppDataLocationFixture.cs" />
<Compile Include="HealthCheck\Checks\DownloadClientCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\DownloadClientCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\DroneFactoryCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\DroneFactoryCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\HealthCheckFixtureExtentions.cs" /> <Compile Include="HealthCheck\Checks\HealthCheckFixtureExtensions.cs" />
<Compile Include="HealthCheck\Checks\ImportMechanismCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\ImportMechanismCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\IndexerCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\IndexerSearchCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\IndexerRssCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\MonoVersionCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\MonoVersionCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\IndexerStatusCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\IndexerStatusCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\RootFolderCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\RootFolderCheckFixture.cs" />
@@ -257,11 +261,9 @@
<Compile Include="IndexerTests\TestIndexer.cs" /> <Compile Include="IndexerTests\TestIndexer.cs" />
<Compile Include="IndexerTests\TestIndexerSettings.cs" /> <Compile Include="IndexerTests\TestIndexerSettings.cs" />
<Compile Include="IndexerTests\IPTorrentsTests\IPTorrentsFixture.cs" /> <Compile Include="IndexerTests\IPTorrentsTests\IPTorrentsFixture.cs" />
<Compile Include="IndexerTests\KickassTorrentsTests\KickassTorrentsFixture.cs" />
<Compile Include="IndexerTests\NyaaTests\NyaaFixture.cs" /> <Compile Include="IndexerTests\NyaaTests\NyaaFixture.cs" />
<Compile Include="IndexerTests\TorrentRssIndexerTests\TorrentRssIndexerFixture.cs" /> <Compile Include="IndexerTests\TorrentRssIndexerTests\TorrentRssIndexerFixture.cs" />
<Compile Include="IndexerTests\TorrentRssIndexerTests\TestTorrentRssIndexer.cs" /> <Compile Include="IndexerTests\TorrentRssIndexerTests\TestTorrentRssIndexer.cs" />
<Compile Include="IndexerTests\WomblesTests\WomblesFixture.cs" />
<Compile Include="IndexerTests\XElementExtensionsFixture.cs" /> <Compile Include="IndexerTests\XElementExtensionsFixture.cs" />
<Compile Include="InstrumentationTests\DatabaseTargetFixture.cs" /> <Compile Include="InstrumentationTests\DatabaseTargetFixture.cs" />
<Compile Include="JobTests\JobRepositoryFixture.cs" /> <Compile Include="JobTests\JobRepositoryFixture.cs" />
@@ -347,7 +349,6 @@
<Compile Include="Profiles\ProfileRepositoryFixture.cs" /> <Compile Include="Profiles\ProfileRepositoryFixture.cs" />
<Compile Include="Profiles\ProfileServiceFixture.cs" /> <Compile Include="Profiles\ProfileServiceFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\XemProxyFixture.cs" />
<Compile Include="ProviderTests\DiskProviderTests\ArchiveProviderFixture.cs" /> <Compile Include="ProviderTests\DiskProviderTests\ArchiveProviderFixture.cs" />
<Compile Include="ProviderTests\DiskScanProviderTests\GetVideoFilesFixture.cs" /> <Compile Include="ProviderTests\DiskScanProviderTests\GetVideoFilesFixture.cs" />
<Compile Include="ProviderTests\RecycleBinProviderTests\CleanupFixture.cs" /> <Compile Include="ProviderTests\RecycleBinProviderTests\CleanupFixture.cs" />
@@ -383,6 +384,7 @@
<Compile Include="UpdateTests\UpdatePackageProviderFixture.cs" /> <Compile Include="UpdateTests\UpdatePackageProviderFixture.cs" />
<Compile Include="UpdateTests\UpdateServiceFixture.cs" /> <Compile Include="UpdateTests\UpdateServiceFixture.cs" />
<Compile Include="XbmcVersionTests.cs" /> <Compile Include="XbmcVersionTests.cs" />
<Compile Include="BulkImport\AddMultiMoviesFixture.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj"> <ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
@@ -418,6 +420,10 @@
<Content Include="Files\imdb_watchlist.xml"> <Content Include="Files\imdb_watchlist.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Files\Indexers\TorrentRss\AlphaRatio.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<SubType>Designer</SubType>
</Content>
<Content Include="License.txt" /> <Content Include="License.txt" />
<None Include="Files\Indexers\BroadcastheNet\RecentFeed.json"> <None Include="Files\Indexers\BroadcastheNet\RecentFeed.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
@@ -558,21 +564,11 @@
<None Include="Files\TestArchive.zip"> <None Include="Files\TestArchive.zip">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<Content Include="Files\Xem\Failure.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Files\Xem\Ids.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Files\Xem\Mappings.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Files\Xem\Names.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="DataAugmentation\SceneNumbering\" />
<Folder Include="Providers\" />
<Folder Include="ProviderTests\UpdateProviderTests\" /> <Folder Include="ProviderTests\UpdateProviderTests\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -587,11 +583,11 @@
<PostBuildEvent> <PostBuildEvent>
</PostBuildEvent> </PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
[TestFixture] [TestFixture]
[Ignore("Series")]
public class AbsoluteEpisodeNumberParserFixture : CoreTest public class AbsoluteEpisodeNumberParserFixture : CoreTest
{ {
[TestCase("[SubDESU]_High_School_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "High School DxD", 7, 0, 0)] [TestCase("[SubDESU]_High_School_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "High School DxD", 7, 0, 0)]
@@ -9,6 +9,7 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
[TestFixture] [TestFixture]
[Ignore("Series")]
public class DailyEpisodeParserFixture : CoreTest public class DailyEpisodeParserFixture : CoreTest
{ {
[TestCase("Conan 2011 04 18 Emma Roberts HDTV XviD BFF", "Conan", 2011, 04, 18)] [TestCase("Conan 2011 04 18 Emma Roberts HDTV XviD BFF", "Conan", 2011, 04, 18)]
@@ -1,4 +1,4 @@
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -11,6 +11,9 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
[TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL", Language.English)] [TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL", Language.English)]
[TestCase("Castle.2009.S01E14.French.HDTV.XviD-LOL", Language.French)] [TestCase("Castle.2009.S01E14.French.HDTV.XviD-LOL", Language.French)]
[TestCase("Ouija.Origin.of.Evil.2016.MULTi.TRUEFRENCH.1080p.BluRay.x264-MELBA", Language.French)]
[TestCase("Everest.2015.FRENCH.VFQ.BDRiP.x264-CNF30", Language.French)]
[TestCase("Showdown.In.Little.Tokyo.1991.MULTI.VFQ.VFF.DTSHD-MASTER.1080p.BluRay.x264-ZombiE", Language.French)]
[TestCase("Castle.2009.S01E14.Spanish.HDTV.XviD-LOL", Language.Spanish)] [TestCase("Castle.2009.S01E14.Spanish.HDTV.XviD-LOL", Language.Spanish)]
[TestCase("Castle.2009.S01E14.German.HDTV.XviD-LOL", Language.German)] [TestCase("Castle.2009.S01E14.German.HDTV.XviD-LOL", Language.German)]
[TestCase("Castle.2009.S01E14.Germany.HDTV.XviD-LOL", Language.English)] [TestCase("Castle.2009.S01E14.Germany.HDTV.XviD-LOL", Language.English)]
@@ -46,10 +49,17 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Castle.2009.S01E14.HDTV.XviD.HUNDUB-LOL", Language.Hungarian)] [TestCase("Castle.2009.S01E14.HDTV.XviD.HUNDUB-LOL", Language.Hungarian)]
[TestCase("Castle.2009.S01E14.HDTV.XviD.ENG.HUN-LOL", Language.Hungarian)] [TestCase("Castle.2009.S01E14.HDTV.XviD.ENG.HUN-LOL", Language.Hungarian)]
[TestCase("Castle.2009.S01E14.HDTV.XviD.HUN-LOL", Language.Hungarian)] [TestCase("Castle.2009.S01E14.HDTV.XviD.HUN-LOL", Language.Hungarian)]
[TestCase("The Danish Girl 2015", Language.English)]
[TestCase("Passengers.2016.German.DL.AC3.Dubbed.1080p.WebHD.h264.iNTERNAL-PsO", Language.German)]
public void should_parse_language(string postTitle, Language language) public void should_parse_language(string postTitle, Language language)
{ {
var result = LanguageParser.ParseLanguage(postTitle); var result = Parser.Parser.ParseMovieTitle(postTitle);
result.Should().Be(language); if (result == null)
{
Parser.Parser.ParseTitle(postTitle).Language.Should().Be(language);
return;
}
result.Language.Should().Be(language);
} }
[TestCase("2 Broke Girls - S01E01 - Pilot.en.sub", Language.English)] [TestCase("2 Broke Girls - S01E01 - Pilot.en.sub", Language.English)]
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
[TestFixture] [TestFixture]
[Ignore("Series")]
public class MiniSeriesEpisodeParserFixture : CoreTest public class MiniSeriesEpisodeParserFixture : CoreTest
{ {
[TestCase("The.Kennedys.Part.2.DSR.XviD-SYS", "The Kennedys", 2)] [TestCase("The.Kennedys.Part.2.DSR.XviD-SYS", "The Kennedys", 2)]
@@ -6,6 +6,7 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
[TestFixture] [TestFixture]
[Ignore("Series")]
public class MultiEpisodeParserFixture : CoreTest public class MultiEpisodeParserFixture : CoreTest
{ {
[TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", "WEEDS", 3, new[] { 1, 2, 3, 4, 5, 6 })] [TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", "WEEDS", 3, new[] { 1, 2, 3, 4, 5, 6 })]
@@ -62,5 +62,30 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
Parser.Parser.ParseTitle(postTitle).SeriesTitle.Should().Be(title); Parser.Parser.ParseTitle(postTitle).SeriesTitle.Should().Be(title);
} }
[TestCase("The.Man.from.U.N.C.L.E.2015.1080p.BluRay.x264-SPARKS", "The Man from U.N.C.L.E.")]
[TestCase("1941.1979.EXTENDED.720p.BluRay.X264-AMIABLE", "1941")]
[TestCase("MY MOVIE (2016) [R][Action, Horror][720p.WEB-DL.AVC.8Bit.6ch.AC3].mkv", "MY MOVIE")]
[TestCase("R.I.P.D.2013.720p.BluRay.x264-SPARKS", "R.I.P.D.")]
[TestCase("V.H.S.2.2013.LIMITED.720p.BluRay.x264-GECKOS", "V.H.S. 2")]
[TestCase("This Is A Movie (1999) [IMDB #] <Genre, Genre, Genre> {ACTORS} !DIRECTOR +MORE_SILLY_STUFF_NO_ONE_NEEDS ?", "This Is A Movie")]
[TestCase("We Are the Best!.2013.720p.H264.mkv", "We Are the Best!")]
[TestCase("(500).Days.Of.Summer.(2009).DTS.1080p.BluRay.x264.NLsubs", "(500) Days Of Summer")]
public void should_parse_movie_title(string postTitle, string title)
{
Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.Should().Be(title);
}
[TestCase("1941.1979.EXTENDED.720p.BluRay.X264-AMIABLE", 1979)]
public void should_parse_movie_year(string postTitle, int year)
{
Parser.Parser.ParseMovieTitle(postTitle).Year.Should().Be(year);
}
[TestCase("The Danish Girl 2015")]
public void should_not_parse_language_in_movie_title(string postTitle)
{
Parser.Parser.ParseMovieTitle(postTitle).Language.Should().Be(Language.English);
}
} }
} }
@@ -14,6 +14,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{ {
[TestFixture] [TestFixture]
[Ignore("Series")]
public class GetEpisodesFixture : TestBase<ParsingService> public class GetEpisodesFixture : TestBase<ParsingService>
{ {
private Series _series; private Series _series;
@@ -18,6 +18,12 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
public class MapFixture : TestBase<ParsingService> public class MapFixture : TestBase<ParsingService>
{ {
private Series _series; private Series _series;
private Movie _movie;
private ParsedMovieInfo _parsedMovieInfo;
private ParsedMovieInfo _wrongYearInfo;
private ParsedMovieInfo _romanTitleInfo;
private ParsedMovieInfo _alternativeTitleInfo;
private MovieSearchCriteria _movieSearchCriteria;
private List<Episode> _episodes; private List<Episode> _episodes;
private ParsedEpisodeInfo _parsedEpisodeInfo; private ParsedEpisodeInfo _parsedEpisodeInfo;
private SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria; private SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria;
@@ -30,6 +36,13 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.With(s => s.CleanTitle = "rock") .With(s => s.CleanTitle = "rock")
.Build(); .Build();
_movie = Builder<Movie>.CreateNew()
.With(m => m.Title = "Mission Impossible 3")
.With(m => m.CleanTitle = "missionimpossible3")
.With(m => m.Year = 2006)
.With(m => m.AlternativeTitles = new List<string> { "Mission Impossible 3: Same same" })
.Build();
_episodes = Builder<Episode>.CreateListOfSize(1) _episodes = Builder<Episode>.CreateListOfSize(1)
.All() .All()
.With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT)) .With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT))
@@ -43,6 +56,31 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
EpisodeNumbers = new[] { 1 } EpisodeNumbers = new[] { 1 }
}; };
_parsedMovieInfo = new ParsedMovieInfo
{
MovieTitle = _movie.Title,
Year = _movie.Year,
};
_wrongYearInfo = new ParsedMovieInfo
{
MovieTitle = _movie.Title,
Year = 1900,
};
_alternativeTitleInfo = new ParsedMovieInfo
{
MovieTitle = _movie.AlternativeTitles.First(),
Year = _movie.Year,
};
_romanTitleInfo = new ParsedMovieInfo
{
MovieTitle = "Mission Impossible III",
Year = _movie.Year,
};
_singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria _singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria
{ {
Series = _series, Series = _series,
@@ -50,27 +88,18 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
SeasonNumber = _episodes.First().SeasonNumber, SeasonNumber = _episodes.First().SeasonNumber,
Episodes = _episodes Episodes = _episodes
}; };
_movieSearchCriteria = new MovieSearchCriteria
{
Movie = _movie
};
} }
private void GivenMatchBySeriesTitle() private void GivenMatchByMovieTitle()
{ {
Mocker.GetMock<ISeriesService>() Mocker.GetMock<IMovieService>()
.Setup(s => s.FindByTitle(It.IsAny<string>())) .Setup(s => s.FindByTitle(It.IsAny<string>()))
.Returns(_series); .Returns(_movie);
}
private void GivenMatchByTvdbId()
{
Mocker.GetMock<ISeriesService>()
.Setup(s => s.FindByTvdbId(It.IsAny<Int32>()))
.Returns(_series);
}
private void GivenMatchByTvRageId()
{
Mocker.GetMock<ISeriesService>()
.Setup(s => s.FindByTvRageId(It.IsAny<int>()))
.Returns(_series);
} }
private void GivenParseResultSeriesDoesntMatchSearchCriteria() private void GivenParseResultSeriesDoesntMatchSearchCriteria()
@@ -79,121 +108,45 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
} }
[Test] [Test]
public void should_lookup_series_by_name() public void should_lookup_Movie_by_name()
{ {
GivenMatchBySeriesTitle(); GivenMatchByMovieTitle();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId); Subject.Map(_parsedMovieInfo, "", null);
Mocker.GetMock<ISeriesService>() Mocker.GetMock<IMovieService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Once()); .Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Once());
} }
[Test] [Test]
public void should_use_tvdbid_when_series_title_lookup_fails() public void should_use_search_criteria_movie_title()
{ {
GivenMatchByTvdbId(); GivenMatchByMovieTitle();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId); Subject.Map(_parsedMovieInfo, "", _movieSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvdbId(It.IsAny<Int32>()), Times.Once());
}
[Test]
public void should_use_tvrageid_when_series_title_lookup_fails()
{
GivenMatchByTvRageId();
Subject.Map(_parsedEpisodeInfo, 0, _series.TvRageId);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Once());
}
[Test]
public void should_not_use_tvrageid_when_scene_naming_exception_exists()
{
GivenMatchByTvRageId();
Mocker.GetMock<ISceneMappingService>()
.Setup(v => v.FindTvdbId(It.IsAny<string>()))
.Returns(10);
var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Never());
result.Series.Should().BeNull();
}
[Test]
public void should_use_search_criteria_series_title()
{
GivenMatchBySeriesTitle();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>() Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never()); .Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
} }
[Test] [Test]
public void should_FindByTitle_when_search_criteria_matching_fails() public void should_not_match_with_wrong_year()
{ {
GivenParseResultSeriesDoesntMatchSearchCriteria(); GivenMatchByMovieTitle();
Subject.Map(_wrongYearInfo, "", _movieSearchCriteria).Movie.Should().BeNull();
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Once());
} }
[Test] [Test]
public void should_FindByTvdbId_when_search_criteria_and_FindByTitle_matching_fails() public void should_match_alternative_title()
{ {
GivenParseResultSeriesDoesntMatchSearchCriteria(); Subject.Map(_alternativeTitleInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
}
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria); [Test]
public void should_match_roman_title()
{
Subject.Map(_romanTitleInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
}
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvdbId(It.IsAny<Int32>()), Times.Once());
}
[Test]
public void should_FindByTvRageId_when_search_criteria_and_FindByTitle_matching_fails()
{
GivenParseResultSeriesDoesntMatchSearchCriteria();
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Once());
}
[Test]
public void should_use_tvdbid_matching_when_alias_is_found()
{
Mocker.GetMock<ISceneMappingService>()
.Setup(s => s.FindTvdbId(It.IsAny<string>()))
.Returns(_series.TvdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
}
[Test]
public void should_use_tvrageid_match_from_search_criteria_when_title_match_fails()
{
GivenParseResultSeriesDoesntMatchSearchCriteria();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
}
} }
} }
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
[TestFixture] [TestFixture]
[Ignore("Series")]//Is this really necessary with movies? I dont think so
public class PathParserFixture : CoreTest public class PathParserFixture : CoreTest
{ {
[TestCase(@"z:\tv shows\battlestar galactica (2003)\Season 3\S03E05 - Collaborators.mkv", 3, 5)] [TestCase(@"z:\tv shows\battlestar galactica (2003)\Season 3\S03E05 - Collaborators.mkv", 3, 5)]
@@ -24,6 +24,8 @@ namespace NzbDrone.Core.Test.ParserTests
new object[] { Quality.Bluray720p }, new object[] { Quality.Bluray720p },
new object[] { Quality.Bluray1080p }, new object[] { Quality.Bluray1080p },
new object[] { Quality.Bluray2160p }, new object[] { Quality.Bluray2160p },
new object[] { Quality.Remux1080p },
new object[] { Quality.Remux2160p },
}; };
public static object[] OtherSourceQualityParserCases = public static object[] OtherSourceQualityParserCases =
@@ -40,6 +42,8 @@ namespace NzbDrone.Core.Test.ParserTests
new object[] { "720p BluRay", Quality.Bluray720p }, new object[] { "720p BluRay", Quality.Bluray720p },
new object[] { "1080p BluRay", Quality.Bluray1080p }, new object[] { "1080p BluRay", Quality.Bluray1080p },
new object[] { "2160p BluRay", Quality.Bluray2160p }, new object[] { "2160p BluRay", Quality.Bluray2160p },
new object[] { "1080p Remux", Quality.Remux1080p },
new object[] { "2160p Remux", Quality.Remux2160p },
}; };
[TestCase("S07E23 .avi ", false)] [TestCase("S07E23 .avi ", false)]
@@ -79,7 +83,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("the.shield.1x13.circles.ws.xvidvd-tns", false)] [TestCase("the.shield.1x13.circles.ws.xvidvd-tns", false)]
[TestCase("the_x-files.9x18.sunshine_days.ac3.ws_dvdrip_xvid-fov.avi", false)] [TestCase("the_x-files.9x18.sunshine_days.ac3.ws_dvdrip_xvid-fov.avi", false)]
[TestCase("[FroZen] Miyuki - 23 [DVD][7F6170E6]", false)] [TestCase("[FroZen] Miyuki - 23 [DVD][7F6170E6]", false)]
[TestCase("Hannibal.S01E05.576p.BluRay.DD5.1.x264-HiSD", false)]
[TestCase("Hannibal.S01E05.480p.BluRay.DD5.1.x264-HiSD", false)] [TestCase("Hannibal.S01E05.480p.BluRay.DD5.1.x264-HiSD", false)]
[TestCase("Heidi Girl of the Alps (BD)(640x480(RAW) (BATCH 1) (1-13)", false)] [TestCase("Heidi Girl of the Alps (BD)(640x480(RAW) (BATCH 1) (1-13)", false)]
[TestCase("[Doki] Clannad - 02 (848x480 XviD BD MP3) [95360783]", false)] [TestCase("[Doki] Clannad - 02 (848x480 XviD BD MP3) [95360783]", false)]
@@ -215,6 +218,27 @@ namespace NzbDrone.Core.Test.ParserTests
ParseAndVerifyQuality(title, Quality.Bluray1080p, proper); ParseAndVerifyQuality(title, Quality.Bluray1080p, proper);
} }
[TestCase("Movie.Name.2004.576p.BDRip.x264-HANDJOB")]
[TestCase("Hannibal.S01E05.576p.BluRay.DD5.1.x264-HiSD")]
public void should_parse_bluray576p_quality(string title)
{
ParseAndVerifyQuality(title, Quality.Bluray576p, false);
}
[TestCase("Contract.to.Kill.2016.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-iFT")]
[TestCase("27.Dresses.2008.REMUX.1080p.Bluray.AVC.DTS-HR.MA.5.1-LEGi0N")]
public void should_parse_remux1080p_quality(string title)
{
ParseAndVerifyQuality(title, Quality.Remux1080p, false);
}
[TestCase("Contract.to.Kill.2016.REMUX.2160p.BluRay.AVC.DTS-HD.MA.5.1-iFT")]
[TestCase("27.Dresses.2008.REMUX.2160p.Bluray.AVC.DTS-HR.MA.5.1-LEGi0N")]
public void should_parse_remux2160p_quality(string title)
{
ParseAndVerifyQuality(title, Quality.Remux2160p, false);
}
//[TestCase("POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false)] //[TestCase("POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
//[TestCase("How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false)] //[TestCase("How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false)]
//[TestCase("The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false)] //[TestCase("The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
@@ -275,6 +299,13 @@ namespace NzbDrone.Core.Test.ParserTests
QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Extension); QualityParser.ParseQuality(title).QualitySource.Should().Be(QualitySource.Extension);
} }
[TestCase("Movie.Title.2016.1080p.KORSUB.WEBRip.x264.AAC2.0-RADARR", "korsub")]
[TestCase("Movie.Title.2016.1080p.KORSUBS.WEBRip.x264.AAC2.0-RADARR", "korsubs")]
public void should_parse_hardcoded_subs(string postTitle, string sub)
{
QualityParser.ParseQuality(postTitle).HardcodedSubs.Should().Be(sub);
}
private void ParseAndVerifyQuality(string title, Quality quality, bool proper) private void ParseAndVerifyQuality(string title, Quality quality, bool proper)
{ {
var result = QualityParser.ParseQuality(title); var result = QualityParser.ParseQuality(title);
@@ -6,6 +6,7 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
[TestFixture] [TestFixture]
[Ignore("Series")]
public class SeasonParserFixture : CoreTest public class SeasonParserFixture : CoreTest
{ {
[TestCase("30.Rock.Season.04.HDTV.XviD-DIMENSION", "30 Rock", 4)] [TestCase("30.Rock.Season.04.HDTV.XviD-DIMENSION", "30 Rock", 4)]
@@ -5,6 +5,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests namespace NzbDrone.Core.Test.ParserTests
{ {
[TestFixture] [TestFixture]
[Ignore("Series")]
public class SeriesTitleInfoFixture : CoreTest public class SeriesTitleInfoFixture : CoreTest
{ {
[Test] [Test]
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
[TestFixture] [TestFixture]
[Ignore("Series")]
public class SingleEpisodeParserFixture : CoreTest public class SingleEpisodeParserFixture : CoreTest
{ {
[TestCase("Sonny.With.a.Chance.S02E15", "Sonny With a Chance", 2, 15)] [TestCase("Sonny.With.a.Chance.S02E15", "Sonny With a Chance", 2, 15)]
@@ -1,39 +1,47 @@
using System.IO; using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
{ {
public class GetVideoFilesFixture : CoreTest<DiskScanService> public class GetVideoFilesFixture : CoreTest<DiskScanService>
{ {
private string[] _files; private string[] _fileNames;
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
_files = new[] _fileNames = new[]
{ {
@"C:\Test\30 Rock1.mkv", @"30 Rock1.mkv",
@"C:\Test\30 Rock2.avi", @"30 Rock2.avi",
@"C:\Test\30 Rock3.MP4", @"30 Rock3.MP4",
@"C:\Test\30 Rock4.wMv", @"30 Rock4.wMv",
@"C:\Test\movie.exe", @"movie.exe",
@"C:\Test\movie" @"movie"
}; };
GivenFiles();
} }
private void GivenFiles() private IEnumerable<string> GetFiles(string folder, string subFolder = "")
{ {
return _fileNames.Select(f => Path.Combine(folder, subFolder, f));
}
private void GivenFiles(IEnumerable<string> files)
{
var filesToReturn = files.ToArray();
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories)) .Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
.Returns(_files); .Returns(filesToReturn);
} }
[Test] [Test]
@@ -73,8 +81,31 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
public void should_return_video_files_only() public void should_return_video_files_only()
{ {
var path = @"C:\Test\"; var path = @"C:\Test\";
GivenFiles(GetFiles(path));
Subject.GetVideoFiles(path).Should().HaveCount(4); Subject.GetVideoFiles(path).Should().HaveCount(4);
} }
[TestCase("Extras")]
[TestCase("@eadir")]
[TestCase("extrafanart")]
[TestCase("Plex Versions")]
[TestCase(".secret")]
[TestCase(".hidden")]
public void should_filter_certain_sub_folders(string subFolder)
{
var path = @"C:\Test\";
var files = GetFiles(path).ToList();
var specialFiles = GetFiles(path, subFolder).ToList();
var allFiles = files.Concat(specialFiles);
var series = Builder<Series>.CreateNew()
.With(s => s.Path = path)
.Build();
var filteredFiles = Subject.FilterFiles(series, allFiles);
filteredFiles.Should().NotContain(specialFiles);
filteredFiles.Count.Should().BeGreaterThan(0);
}
} }
} }
@@ -1,54 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.DataAugmentation.Xem;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common.Categories;
namespace NzbDrone.Core.Test.Providers
{
[TestFixture]
[IntegrationTest]
public class XemProxyFixture : CoreTest<XemProxy>
{
[SetUp]
public void Setup()
{
UseRealHttp();
}
[Test]
public void get_series_ids()
{
var ids = Subject.GetXemSeriesIds();
ids.Should().NotBeEmpty();
ids.Should().Contain(i => i == 73141);
}
[TestCase(12345, Description = "invalid id")]
[TestCase(279042, Description = "no single connection")]
public void should_return_empty_when_known_error(int id)
{
Subject.GetSceneTvdbMappings(id).Should().BeEmpty();
}
[TestCase(82807)]
[TestCase(73141, Description = "American Dad!")]
public void should_get_mapping(int seriesId)
{
var result = Subject.GetSceneTvdbMappings(seriesId);
result.Should().NotBeEmpty();
result.Should().OnlyContain(c => c.Scene != null);
result.Should().OnlyContain(c => c.Tvdb != null);
}
[TestCase(78916)]
public void should_filter_out_episodes_without_scene_mapping(int seriesId)
{
var result = Subject.GetSceneTvdbMappings(seriesId);
result.Should().NotContain(c => c.Tvdb == null);
}
}
}
@@ -1,12 +1,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.RootFolders; using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.RootFolderTests namespace NzbDrone.Core.Test.RootFolderTests
@@ -103,5 +107,48 @@ namespace NzbDrone.Core.Test.RootFolderTests
Assert.Throws<InvalidOperationException>(() => Subject.Add(new RootFolder { Path = path })); Assert.Throws<InvalidOperationException>(() => Subject.Add(new RootFolder { Path = path }));
} }
[TestCase("$recycle.bin")]
[TestCase("system volume information")]
[TestCase("recycler")]
[TestCase("lost+found")]
[TestCase(".appledb")]
[TestCase(".appledesktop")]
[TestCase(".appledouble")]
[TestCase("@eadir")]
[TestCase(".grab")]
public void should_get_root_folder_with_subfolders_excluding_special_sub_folders(string subFolder)
{
var rootFolder = Builder<RootFolder>.CreateNew()
.With(r => r.Path = @"C:\Test\TV")
.Build();
var subFolders = new[]
{
"Series1",
"Series2",
"Series3",
subFolder
};
var folders = subFolders.Select(f => Path.Combine(@"C:\Test\TV", f)).ToArray();
Mocker.GetMock<IRootFolderRepository>()
.Setup(s => s.Get(It.IsAny<int>()))
.Returns(rootFolder);
Mocker.GetMock<ISeriesService>()
.Setup(s => s.GetAllSeries())
.Returns(new List<Series>());
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetDirectories(rootFolder.Path))
.Returns(folders);
var unmappedFolders = Subject.Get(rootFolder.Id).UnmappedFolders;
unmappedFolders.Count.Should().BeGreaterThan(0);
unmappedFolders.Should().NotContain(u => u.Name == subFolder);
}
} }
} }
@@ -1,40 +1,39 @@
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Organizer; using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests
{ {
[TestFixture] [TestFixture]
public class AddSeriesFixture : CoreTest<SeriesService> public class AddSeriesFixture : CoreTest<SeriesService>
{ {
private Series fakeSeries; private Series fakeSeries;
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
fakeSeries = Builder<Series>.CreateNew().Build(); fakeSeries = Builder<Series>.CreateNew().Build();
} }
[Test] [Test]
public void series_added_event_should_have_proper_path() public void series_added_event_should_have_proper_path()
{ {
fakeSeries.Path = null; fakeSeries.Path = null;
fakeSeries.RootFolderPath = @"C:\Test\TV"; fakeSeries.RootFolderPath = @"C:\Test\TV";
Mocker.GetMock<IBuildFileNames>() Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.GetSeriesFolder(fakeSeries, null)) .Setup(s => s.GetSeriesFolder(fakeSeries, null))
.Returns(fakeSeries.Title); .Returns(fakeSeries.Title);
var series = Subject.AddSeries(fakeSeries); var series = Subject.AddSeries(fakeSeries);
series.Path.Should().NotBeNull(); series.Path.Should().NotBeNull();
VerifyEventPublished<SeriesAddedEvent>(); }
}
}
}
} }
@@ -105,12 +105,69 @@ namespace NzbDrone.Core.Configuration
set { SetValue("RssSyncInterval", value); } set { SetValue("RssSyncInterval", value); }
} }
public int AvailabilityDelay
{
get { return GetValueInt("AvailabilityDelay",0); }
set { SetValue("AvailabilityDelay", value); }
}
public int NetImportSyncInterval public int NetImportSyncInterval
{ {
get { return GetValueInt("NetImportSyncInterval", 60); } get { return GetValueInt("NetImportSyncInterval", 60); }
set { SetValue("NetImportSyncInterval", value); } set { SetValue("NetImportSyncInterval", value); }
} }
public string TraktAuthToken
{
get { return GetValue("TraktAuthToken", string.Empty); }
set { SetValue("TraktAuthToken", value); }
}
public string TraktRefreshToken
{
get { return GetValue("TraktRefreshToken", string.Empty); }
set {SetValue("TraktRefreshToken", value); }
}
public int TraktTokenExpiry
{
get { return GetValueInt("TraktTokenExpiry", 0); }
set { SetValue("TraktTokenExpiry", value); }
}
public string NewTraktAuthToken
{
get {return GetValue("NewTraktAuthToken", string.Empty); }
set { SetValue("NewTraktAuthToken", value); }
}
public string NewTraktRefreshToken
{
get {return GetValue("NewTraktRefreshToken", string.Empty); }
set { SetValue("NewTraktRefreshToken", value); }
}
public int NewTraktTokenExpiry
{
get {return GetValueInt("NewTraktTokenExpiry", 0); }
set { SetValue("NewTraktTokenExpiry", value); }
}
public string ListSyncLevel
{
get { return GetValue("ListSyncLevel", "disabled"); }
set { SetValue("ListSyncLevel", value); }
}
public string ImportExclusions
{
get { return GetValue("ImportExclusions", string.Empty); }
set { SetValue("ImportExclusions", value); }
}
public int MinimumAge public int MinimumAge
{ {
@@ -46,7 +46,17 @@ namespace NzbDrone.Core.Configuration
int RssSyncInterval { get; set; } int RssSyncInterval { get; set; }
int MinimumAge { get; set; } int MinimumAge { get; set; }
int AvailabilityDelay { get; set; }
int NetImportSyncInterval { get; set; } int NetImportSyncInterval { get; set; }
string ListSyncLevel { get; set; }
string ImportExclusions { get; set; }
string TraktAuthToken { get; set; }
string TraktRefreshToken { get; set; }
int TraktTokenExpiry { get; set; }
string NewTraktAuthToken { get; set; }
string NewTraktRefreshToken {get; set; }
int NewTraktTokenExpiry { get; set; }
//UI //UI
int FirstDayOfWeek { get; set; } int FirstDayOfWeek { get; set; }
@@ -56,6 +66,7 @@ namespace NzbDrone.Core.Configuration
string LongDateFormat { get; set; } string LongDateFormat { get; set; }
string TimeFormat { get; set; } string TimeFormat { get; set; }
bool ShowRelativeDates { get; set; } bool ShowRelativeDates { get; set; }
bool EnableColorImpairedMode { get; set; } bool EnableColorImpairedMode { get; set; }
//Internal //Internal
@@ -1,9 +0,0 @@
namespace NzbDrone.Core.DataAugmentation.Xem.Model
{
public class XemResult<T>
{
public string Result { get; set; }
public T Data { get; set; }
public string Message { get; set; }
}
}
@@ -1,8 +0,0 @@
namespace NzbDrone.Core.DataAugmentation.Xem.Model
{
public class XemSceneTvdbMapping
{
public XemValues Scene { get; set; }
public XemValues Tvdb { get; set; }
}
}
@@ -1,9 +0,0 @@
namespace NzbDrone.Core.DataAugmentation.Xem.Model
{
public class XemValues
{
public int Season { get; set; }
public int Episode { get; set; }
public int Absolute { get; set; }
}
}
@@ -1,127 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.DataAugmentation.Xem.Model;
namespace NzbDrone.Core.DataAugmentation.Xem
{
public interface IXemProxy
{
List<int> GetXemSeriesIds();
List<XemSceneTvdbMapping> GetSceneTvdbMappings(int id);
List<SceneMapping> GetSceneTvdbNames();
}
public class XemProxy : IXemProxy
{
private const string ROOT_URL = "http://thexem.de/map/";
private readonly Logger _logger;
private readonly IHttpClient _httpClient;
private readonly IHttpRequestBuilderFactory _xemRequestBuilder;
private static readonly string[] IgnoredErrors = { "no single connection", "no show with the tvdb_id" };
public XemProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
_xemRequestBuilder = new HttpRequestBuilder(ROOT_URL)
.AddSuffixQueryParam("origin", "tvdb")
.CreateFactory();
}
public List<int> GetXemSeriesIds()
{
_logger.Debug("Fetching Series IDs from");
var request = _xemRequestBuilder.Create()
.Resource("/havemap")
.Build();
var response = _httpClient.Get<XemResult<List<string>>>(request).Resource;
CheckForFailureResult(response);
return response.Data.Select(d =>
{
int tvdbId = 0;
int.TryParse(d, out tvdbId);
return tvdbId;
}).Where(t => t > 0).ToList();
}
public List<XemSceneTvdbMapping> GetSceneTvdbMappings(int id)
{
_logger.Debug("Fetching Mappings for: {0}", id);
var request = _xemRequestBuilder.Create()
.Resource("/all")
.AddQueryParam("id", id)
.Build();
var response = _httpClient.Get<XemResult<List<XemSceneTvdbMapping>>>(request).Resource;
return response.Data.Where(c => c.Scene != null).ToList();
}
public List<SceneMapping> GetSceneTvdbNames()
{
_logger.Debug("Fetching alternate names");
var request = _xemRequestBuilder.Create()
.Resource("/allNames")
.AddQueryParam("seasonNumbers", true)
.Build();
var response = _httpClient.Get<XemResult<Dictionary<int, List<JObject>>>>(request).Resource;
var result = new List<SceneMapping>();
foreach (var series in response.Data)
{
foreach (var name in series.Value)
{
foreach (var n in name)
{
int seasonNumber;
if (!int.TryParse(n.Value.ToString(), out seasonNumber))
{
continue;
}
//hack to deal with Fate/Zero
if (series.Key == 79151 && seasonNumber > 1)
{
continue;
}
result.Add(new SceneMapping
{
Title = n.Key,
SearchTerm = n.Key,
SceneSeasonNumber = seasonNumber,
TvdbId = series.Key
});
}
}
}
return result;
}
private static void CheckForFailureResult<T>(XemResult<T> response)
{
if (response.Result.Equals("failure", StringComparison.InvariantCultureIgnoreCase) &&
!IgnoredErrors.Any(knowError => response.Message.Contains(knowError)))
{
throw new Exception("Error response received from Xem: " + response.Message);
}
}
}
}
@@ -1,243 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.DataAugmentation.Xem
{
public class XemService : ISceneMappingProvider, IHandle<SeriesUpdatedEvent>, IHandle<SeriesRefreshStartingEvent>
{
private readonly IEpisodeService _episodeService;
private readonly IXemProxy _xemProxy;
private readonly ISeriesService _seriesService;
private readonly Logger _logger;
private readonly ICachedDictionary<bool> _cache;
public XemService(IEpisodeService episodeService,
IXemProxy xemProxy,
ISeriesService seriesService, ICacheManager cacheManager, Logger logger)
{
_episodeService = episodeService;
_xemProxy = xemProxy;
_seriesService = seriesService;
_logger = logger;
_cache = cacheManager.GetCacheDictionary<bool>(GetType(), "mappedTvdbid");
}
private void PerformUpdate(Series series)
{
_logger.Debug("Updating scene numbering mapping for: {0}", series);
try
{
var mappings = _xemProxy.GetSceneTvdbMappings(series.TvdbId);
if (!mappings.Any() && !series.UseSceneNumbering)
{
_logger.Debug("Mappings for: {0} are empty, skipping", series);
return;
}
var episodes = _episodeService.GetEpisodeBySeries(series.Id);
foreach (var episode in episodes)
{
episode.SceneAbsoluteEpisodeNumber = null;
episode.SceneSeasonNumber = null;
episode.SceneEpisodeNumber = null;
episode.UnverifiedSceneNumbering = false;
}
foreach (var mapping in mappings)
{
_logger.Debug("Setting scene numbering mappings for {0} S{1:00}E{2:00}", series, mapping.Tvdb.Season, mapping.Tvdb.Episode);
var episode = episodes.SingleOrDefault(e => e.SeasonNumber == mapping.Tvdb.Season && e.EpisodeNumber == mapping.Tvdb.Episode);
if (episode == null)
{
_logger.Debug("Information hasn't been added to TheTVDB yet, skipping.");
continue;
}
episode.SceneAbsoluteEpisodeNumber = mapping.Scene.Absolute;
episode.SceneSeasonNumber = mapping.Scene.Season;
episode.SceneEpisodeNumber = mapping.Scene.Episode;
}
if (episodes.Any(v => v.SceneEpisodeNumber.HasValue && v.SceneSeasonNumber != 0))
{
ExtrapolateMappings(series, episodes, mappings);
}
_episodeService.UpdateEpisodes(episodes);
series.UseSceneNumbering = mappings.Any();
_seriesService.UpdateSeries(series);
_logger.Debug("XEM mapping updated for {0}", series);
}
catch (Exception ex)
{
_logger.Error(ex, "Error updating scene numbering mappings for: " + series);
}
}
private void ExtrapolateMappings(Series series, List<Episode> episodes, List<Model.XemSceneTvdbMapping> mappings)
{
var mappedEpisodes = episodes.Where(v => v.SeasonNumber != 0 && v.SceneEpisodeNumber.HasValue).ToList();
var mappedSeasons = new HashSet<int>(mappedEpisodes.Select(v => v.SeasonNumber).Distinct());
var sceneEpisodeMappings = mappings.ToLookup(v => v.Scene.Season)
.ToDictionary(v => v.Key, e => new HashSet<int>(e.Select(v => v.Scene.Episode)));
var firstTvdbEpisodeBySeason = mappings.ToLookup(v => v.Tvdb.Season)
.ToDictionary(v => v.Key, e => e.Min(v => v.Tvdb.Episode));
var lastSceneSeason = mappings.Select(v => v.Scene.Season).Max();
var lastTvdbSeason = mappings.Select(v => v.Tvdb.Season).Max();
// Mark all episodes not on the xem as unverified.
foreach (var episode in episodes)
{
if (episode.SeasonNumber == 0) continue;
if (episode.SceneEpisodeNumber.HasValue) continue;
if (mappedSeasons.Contains(episode.SeasonNumber))
{
// Mark if a mapping exists for an earlier episode in this season.
if (firstTvdbEpisodeBySeason[episode.SeasonNumber] <= episode.EpisodeNumber)
{
episode.UnverifiedSceneNumbering = true;
continue;
}
// Mark if a mapping exists with a scene number to this episode.
if (sceneEpisodeMappings.ContainsKey(episode.SeasonNumber) &&
sceneEpisodeMappings[episode.SeasonNumber].Contains(episode.EpisodeNumber))
{
episode.UnverifiedSceneNumbering = true;
continue;
}
}
else if (lastSceneSeason != lastTvdbSeason && episode.SeasonNumber > lastTvdbSeason)
{
episode.UnverifiedSceneNumbering = true;
}
}
foreach (var episode in episodes)
{
if (episode.SeasonNumber == 0) continue;
if (episode.SceneEpisodeNumber.HasValue) continue;
if (episode.SeasonNumber < lastTvdbSeason) continue;
if (!episode.UnverifiedSceneNumbering) continue;
var seasonMappings = mappings.Where(v => v.Tvdb.Season == episode.SeasonNumber).ToList();
if (seasonMappings.Any(v => v.Tvdb.Episode >= episode.EpisodeNumber))
{
continue;
}
if (seasonMappings.Any())
{
var lastEpisodeMapping = seasonMappings.OrderBy(v => v.Tvdb.Episode).Last();
var lastSceneSeasonMapping = mappings.Where(v => v.Scene.Season == lastEpisodeMapping.Scene.Season).OrderBy(v => v.Scene.Episode).Last();
if (lastSceneSeasonMapping.Tvdb.Season == 0)
{
continue;
}
var offset = episode.EpisodeNumber - lastEpisodeMapping.Tvdb.Episode;
episode.SceneSeasonNumber = lastEpisodeMapping.Scene.Season;
episode.SceneEpisodeNumber = lastEpisodeMapping.Scene.Episode + offset;
episode.SceneAbsoluteEpisodeNumber = lastEpisodeMapping.Scene.Absolute + offset;
}
else if (lastTvdbSeason != lastSceneSeason)
{
var offset = episode.SeasonNumber - lastTvdbSeason;
episode.SceneSeasonNumber = lastSceneSeason + offset;
episode.SceneEpisodeNumber = episode.EpisodeNumber;
// TODO: SceneAbsoluteEpisodeNumber.
}
}
}
private void UpdateXemSeriesIds()
{
try
{
var ids = _xemProxy.GetXemSeriesIds();
if (ids.Any())
{
_cache.Update(ids.ToDictionary(v => v.ToString(), v => true));
return;
}
_cache.ExtendTTL();
_logger.Warn("Failed to update Xem series list.");
}
catch (Exception ex)
{
_cache.ExtendTTL();
_logger.Warn(ex, "Failed to update Xem series list.");
}
}
public List<SceneMapping> GetSceneMappings()
{
var mappings = _xemProxy.GetSceneTvdbNames();
return mappings.Where(m =>
{
int id;
if (int.TryParse(m.Title, out id))
{
_logger.Debug("Skipping all numeric name: {0} for {1}", m.Title, m.TvdbId);
return false;
}
return true;
}).ToList();
}
public void Handle(SeriesUpdatedEvent message)
{
if (_cache.IsExpired(TimeSpan.FromHours(3)))
{
UpdateXemSeriesIds();
}
if (_cache.Count == 0)
{
_logger.Debug("Scene numbering is not available");
return;
}
if (!_cache.Find(message.Series.TvdbId.ToString()) && !message.Series.UseSceneNumbering)
{
_logger.Debug("Scene numbering is not available for {0} [{1}]", message.Series.Title, message.Series.TvdbId);
return;
}
PerformUpdate(message.Series);
}
public void Handle(SeriesRefreshStartingEvent message)
{
if (message.ManualTrigger && _cache.IsExpired(TimeSpan.FromMinutes(1)))
{
UpdateXemSeriesIds();
}
}
}
}
@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(127)]
public class remove_wombles : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.FromTable("Indexers").Row(new { Implementation = "Wombles" });
}
}
}
@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(128)]
public class remove_kickass : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.FromTable("Indexers").Row(new { Implementation = "Kickass Torrents" });
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Data;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(129)]
public class add_parsed_movie_info_to_pending_release : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("PendingReleases").AddColumn("ParsedMovieInfo").AsString().Nullable();
}
}
}
@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(129)]
public class remove_kickass_again : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.FromTable("Indexers").Row(new { Implementation = "KickassTorrents" });
}
}
}
@@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(127)]
public class remove_wombles_kickass : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.FromTable("Indexers").Row(new { Implementation = "Wombles" });
Delete.FromTable("Indexers").Row(new { Implementation = "KickassTorrents" });
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Data;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(131)]
public class make_parsed_episode_info_nullable : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("PendingReleases").AlterColumn("ParsedEpisodeInfo").AsString().Nullable();
}
}
}
@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(132)]
public class rename_torrent_downloadstation : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.Sql("UPDATE DownloadClients SET Implementation = 'TorrentDownloadStation' WHERE Implementation = 'DownloadStation';");
}
}
}
@@ -0,0 +1,23 @@
using FluentMigrator;
//using FluentMigrator.Expressions;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(133)]
public class add_minimumavailability : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
if (!this.Schema.Schema("dbo").Table("NetImport").Column("MinimumAvailability").Exists())
{
Alter.Table("NetImport").AddColumn("MinimumAvailability").AsInt32().WithDefaultValue(MovieStatusType.PreDB);
}
if (!this.Schema.Schema("dbo").Table("Movies").Column("MinimumAvailability").Exists())
{
Alter.Table("Movies").AddColumn("MinimumAvailability").AsInt32().WithDefaultValue(MovieStatusType.PreDB);
}
}
}
}

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