Compare commits

..

235 Commits

Author SHA1 Message Date
Ruud
796aff4514 Remove login_opener 2014-01-11 13:58:15 +01:00
Ruud
2a2fe448e7 Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/media/movie/_base/main.py
	couchpotato/core/providers/torrent/bitsoup/main.py
	couchpotato/core/providers/torrent/iptorrents/main.py
	couchpotato/core/providers/torrent/sceneaccess/main.py
	couchpotato/core/providers/torrent/torrentleech/main.py
	couchpotato/core/providers/torrent/torrentshack/main.py
2014-01-11 13:55:12 +01:00
Ruud Burger
23967d11dd Merge pull request #2681 from fuzeman/tv_searcher
[TV] Cleanup retrieval of media query and title
2014-01-11 04:02:04 -08:00
Dean Gardiner
7a3251f649 Added new version of 'library.title' to return the title of the media excluding year and identifiers. 2014-01-11 16:36:29 +13:00
Dean Gardiner
9ba8910281 Renamed 'library.title' to 'library.query' 2014-01-11 16:23:33 +13:00
Dean Gardiner
e83a3cf263 Renamed movie library.title 'include_identifier' to 'include_year', show library.title defaults to 'condense' enabled now. 2014-01-11 15:55:20 +13:00
Dean Gardiner
b3c2945d9b 'related_libraries' and 'root_library' references are now added to child libraries. 2014-01-11 15:11:32 +13:00
Dean Gardiner
fc3cf08675 Moved 'searcher.get_search_title' to 'library.title', include_identifier is enabled by default now and title condensing can be enabled by the 'condense' parameter now. 2014-01-11 15:11:31 +13:00
Ruud Burger
8162cd31b7 Merge pull request #2652 from nikagl/patch-1
Library object has media instead of movies
2014-01-06 13:27:12 -08:00
nikagl
1ea6fdc9a7 Library object has media instead of movies
Make the renamer work again by scanning the media instead of non-existent movies attribute in the library object (fixing error: AttributeError: 'Library' object has no attribute 'movies')
2014-01-01 20:54:34 +01:00
Ruud Burger
1b0c9f40cc Merge pull request #2647 from nikagl/patch-1
Update main.py
2013-12-31 02:21:41 -08:00
nikagl
c0111a467b Update main.py
Release table has media_id column, not movie_id
2013-12-31 11:02:32 +01:00
Ruud Burger
64175151f8 Merge pull request #2634 from dkboy/tv_bitsoup
Updated Bitsoup provider to include TV support
2013-12-29 15:35:50 -08:00
dkboy
586957e840 Updated Bitsoup provider to include TV support
Updated Bitsoup Provider to include TV support as well as Movies.
2013-12-28 21:30:49 +13:00
Joel Kåberg
f2fc775963 Revert "Merge branches 'develop' and 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv"
This reverts commit b8bce948c8, reversing
changes made to 0a996857dd.
2013-12-20 02:29:15 +01:00
Joel Kåberg
b8bce948c8 Merge branches 'develop' and 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv
Conflicts:
	couchpotato/core/media/movie/_base/main.py
	couchpotato/core/providers/torrent/yify/main.py
2013-12-20 02:15:08 +01:00
Joel Kåberg
0a996857dd Merge branches 'develop' and 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv
Conflicts:
	couchpotato/core/plugins/release/main.py
	couchpotato/core/plugins/renamer/main.py
2013-12-19 20:57:12 +01:00
Joel Kåberg
26509f614c use identifier instead 2013-12-15 11:12:47 +01:00
Joel Kåberg
3e28d5a936 use year as identifier for movies 2013-12-15 11:02:57 +01:00
Joel Kåberg
95ff427873 ignore series from omdbapi (for now?) 2013-12-15 10:05:51 +01:00
Joel Kåberg
8ed10037df Merge pull request #2602 from saxicek/tv_tsh
update TorrentShack for tv branch
2013-12-15 00:37:59 -08:00
sax
7a090dd4a2 update TorrentShack for tv branch 2013-12-15 00:08:18 +01:00
Joel Kåberg
49f34cb48d movie > media 2013-12-14 23:57:20 +01:00
Joel Kåberg
2a76de50dd Merge branches 'develop' and 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv
Conflicts:
	couchpotato/core/plugins/renamer/main.py
2013-12-14 23:43:29 +01:00
Joel Kåberg
8adf7fc600 Merge remote-tracking branch 'remotes/origin/develop' into tv 2013-12-14 21:24:07 +01:00
Joel Kåberg
f4c053f56f fix season search for SCC provider 2013-12-14 21:22:46 +01:00
Joel Kåberg
5cb5a1677d Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv 2013-12-14 20:35:03 +01:00
Joel Kåberg
9fb9f0ef5b Merge pull request #2599 from fuzeman/tv_searcher
[TV] Moved matcher to core/media, updated NZBIndex provider
2013-12-13 08:11:10 -08:00
Dean Gardiner
242d69a981 The nzbindex provider now uses the caper usenet parser to get release names from usenet subjects. 2013-12-13 16:31:26 +13:00
Dean Gardiner
eb151a4c5d Updated Caper to v0.3.1 2013-12-13 15:24:40 +13:00
Dean Gardiner
2520b19798 Fixed bug in searcher where episode searches would be triggered if a season release has already been snatched at a better quality 2013-12-09 13:33:25 +13:00
Dean Gardiner
319c9e979a Split ShowMatcher into Episode and Season matchers, updated correctIdentifier method so there should be less false matches now. 2013-12-09 13:32:43 +13:00
Dean Gardiner
93aa5b1920 Updated Caper to v0.2.9 2013-12-09 11:23:57 +13:00
Dean Gardiner
f648af66a6 Moved matcher plugin to core/media, moved some matcher related functions from ShowSearcher to ShowMatcher 2013-12-08 22:40:39 +13:00
Joel Kåberg
7c4185e1fa Merge branch 'develop' into tv 2013-12-07 23:40:42 +01:00
Joel Kåberg
0fb06a3fd3 Merge pull request #2577 from fuzeman/tv_searcher
[TV] Season pack matching, better show search triggering
2013-12-07 01:10:27 -08:00
Dean Gardiner
1e39d643a8 Searching for a show now triggers searching for all the seasons, Season searches that fail to find anything now trigger individual episode searches. 2013-12-07 21:23:10 +13:00
Dean Gardiner
69d58663ef Profile and categories on seasons and episodes are now set to the same value as the Show 2013-12-07 21:21:15 +13:00
Dean Gardiner
e59b53fab2 Searching is now deferred until the entire show has been loaded into the database 2013-12-07 18:48:26 +13:00
Dean Gardiner
a66f6f0166 Fixed reference to 'movie.restatus' (should be 'media.restatus'), minor formatting changes 2013-12-07 18:11:06 +13:00
Dean Gardiner
1344f03b16 Fixed matcher bug when matching resolution on ['480p', None] 2013-12-07 18:11:05 +13:00
Dean Gardiner
a23c409939 Updated Caper to v0.2.6 2013-12-07 18:11:05 +13:00
Dean Gardiner
a6b1cc833f Added more TV qualities for testing (arh.. bit of a mess) 2013-12-07 18:11:04 +13:00
Joel Kåberg
d2c7e3ef56 update Nzbindex for tv branch 2013-12-06 12:55:02 +01:00
Joel Kåberg
6c87008d7b update Nzbclub for tv branch 2013-12-06 12:46:36 +01:00
Joel Kåberg
6b3af21e45 update Binsearch for tv branch 2013-12-06 12:38:54 +01:00
Joel Kåberg
5a5cc0005c Merge pull request #2574 from fuzeman/tv_searcher
[TV] Matching, serialization and UI notification fixes
2013-12-06 03:18:04 -08:00
Joel Kåberg
d65117c0e3 update TorrentPotato for tv branch 2013-12-06 12:16:58 +01:00
Dean Gardiner
d8884bb655 Changed '.searcher.single' call to use search_dict for media serialization 2013-12-06 23:58:47 +13:00
Dean Gardiner
afe9aed2eb Fixed bug where media default_dict contained related and root library attributes. 2013-12-06 23:58:45 +13:00
Dean Gardiner
01e64e989e Updated Caper to v0.2.5 - fixes 'H 264' tag bug 2013-12-06 23:58:44 +13:00
Dean Gardiner
9496df9e9d Fixed a bug where matching show names with a year would fail 2013-12-06 23:58:43 +13:00
Joel Kåberg
8b4c67b977 update Yify for tv branch 2013-12-06 11:44:59 +01:00
Joel Kåberg
f77a8f5573 update PassThePopcorn for tv branch 2013-12-06 11:40:58 +01:00
Joel Kåberg
de8aefebb7 update Bit-HDTV for tv branch 2013-12-06 11:03:24 +01:00
Joel Kåberg
8f0d22a6f2 update TPB for tv branch 2013-12-06 10:46:08 +01:00
Joel Kåberg
721190028b not needed 2013-12-06 09:34:02 +01:00
Joel Kåberg
50e565142e typo 2013-12-06 09:27:02 +01:00
Joel Kåberg
bead3e2b07 update PublicHD for tv branch 2013-12-06 09:26:20 +01:00
Joel Kåberg
71aa0cbb9a use buildUrl 2013-12-06 09:09:53 +01:00
Joel Kåberg
8de19cbd52 fixes 2013-12-06 08:19:13 +01:00
Joel Kåberg
8573832ff7 fixes 2013-12-06 08:05:34 +01:00
Joel Kåberg
7c1d3f8762 fixes 2013-12-06 08:05:15 +01:00
Joel Kåberg
9cd1adcdee fixes 2013-12-06 08:04:58 +01:00
Joel Kåberg
f017ac9dca use searcher.get_search_title 2013-12-06 07:46:50 +01:00
Joel Kåberg
907704e45f fix self.getSearchTitle() 2013-12-06 07:44:16 +01:00
Joel Kåberg
b17f937389 use include_identifier 2013-12-06 07:40:15 +01:00
Joel Kåberg
f591c56dd4 Merge pull request #2570 from fuzeman/tv_searcher
[TV] WEB-DL matcher fix, updated 'searcher.get_search_title'
2013-12-05 22:14:24 -08:00
Dean Gardiner
2fd54901e7 Added optional parameter 'include_identifier' to the 'searcher.get_search_title' event handler. 2013-12-06 14:04:00 +13:00
Dean Gardiner
1bf6c5a82e Changed 'searcher.get_search_title' to accept a 'library' instead of the 'media' as a parameter. 2013-12-06 13:53:56 +13:00
Dean Gardiner
45484461b5 Adjusted Matcher.chainMatch to support 'WEB DL' tags 2013-12-06 13:50:01 +13:00
Dean Gardiner
aa394f59ae Updated Caper to v0.2.4 2013-12-06 13:50:00 +13:00
Joel Kåberg
717111f5d2 cleanup Newznab provider 2013-12-05 21:11:45 +01:00
Joel Kåberg
e3461dc35f updated TorrentDay for tv branch 2013-12-05 17:13:34 +01:00
Joel Kåberg
9b834f62a9 updated Torrentleech for tv branch 2013-12-05 16:55:34 +01:00
Joel Kåberg
935938474c SCC Provider: remove debug info 2013-12-05 16:23:07 +01:00
Joel Kåberg
6573196186 update SCC for tv branch 2013-12-05 16:21:58 +01:00
Joel Kåberg
9a07f2ed65 use searcher.get_search_title and library.identifier (not present in movie library module?) 2013-12-05 15:40:21 +01:00
Joel Kåberg
613ff3b729 updated newznab provider for tv branch. see inline comments 2013-12-05 14:09:35 +01:00
Ruud Burger
def62fc865 Merge pull request #2568 from fuzeman/tv_searcher
[TV] Fixed bug with Library serialization when adding shows
2013-12-05 00:42:13 -08:00
Dean Gardiner
037c355836 Fixed bug with Library serialization when adding shows 2013-12-05 16:23:21 +13:00
Joel Kåberg
180b2bbffe Merge pull request #2549 from fuzeman/tv_searcher
[TV] Searcher cleanup and matcher updates
2013-12-03 23:16:11 -08:00
Dean Gardiner
143dcad4f3 Fixed incorrect reference to library 'season' and 'episode' attributes. 2013-12-04 19:50:48 +13:00
Dean Gardiner
b0e352ab6d Updated Caper to v0.2.3 and Logr to v0.2.2 to greatly improve matching performance 2013-12-04 19:29:02 +13:00
Dean Gardiner
5ea7dc5920 Moved 'searcher.get_media_identifier' into season and episode libraries as 'library.identifier' 2013-12-04 17:15:08 +13:00
Dean Gardiner
3c675b5b8a searcher and matcher now uses the new related_libraries and root_library from media instead of using extra db queries 2013-12-02 23:27:26 +13:00
Dean Gardiner
11ea9b4e91 related_libraries are now only included on searches and added the root_library attribute 2013-12-02 23:26:31 +13:00
Dean Gardiner
e8a2139ecf Related libraries are now merged into {<type>: [<library>,...]} type 2013-12-02 21:18:48 +13:00
Dean Gardiner
dc57d7b6d1 Added related_libraries to Library model. 2013-12-01 20:25:33 +13:00
Dean Gardiner
0925f1312d Fixed refresh action - changed show searcher to bind to 'season' and 'episode' media types for '.searcher.single' as well. 2013-12-01 20:22:07 +13:00
Dean Gardiner
efc02f66f5 Changed the IPTorrents show provider into a new season and episode provider, removed grouped cat_ids 2013-12-01 20:20:19 +13:00
Ruud
9ce8ffc14b movie_id > media_id 2013-11-30 16:52:08 +01:00
Ruud
bab07a05e7 Merge branch 'refs/heads/develop' into tv 2013-11-30 16:48:52 +01:00
Ruud
1df9f7c83f Merge branch 'refs/heads/develop' into tv 2013-11-30 16:14:19 +01:00
Ruud
efdf77ef6c Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/media/movie/_base/main.py
	couchpotato/core/plugins/release/main.py
	couchpotato/core/plugins/renamer/main.py
2013-11-30 12:44:13 +01:00
Ruud Burger
a989c93505 Merge pull request #2523 from fuzeman/tv_searcher
[TV] Merge fixes, removed get_media_searcher_id event
2013-11-25 07:38:08 -08:00
Dean Gardiner
d122bd1b43 Removed 'searcher.download' (method was moved to the release plugin) 2013-11-25 19:47:05 +13:00
Dean Gardiner
ab81824f4c Minor changes to matcher and added extra show searcher logging 2013-11-25 19:29:29 +13:00
Dean Gardiner
4eb73e3609 Renamed Release.movie references to Release.media 2013-11-25 19:28:43 +13:00
Dean Gardiner
6bcb279f0e Updated Caper library 2013-11-25 17:37:08 +13:00
Dean Gardiner
f446c8ed33 Updated QueryCondenser library 2013-11-25 17:07:54 +13:00
Dean Gardiner
10a34f2b69 Removed the use of the 'searcher.get_media_searcher_id' event 2013-11-25 16:20:03 +13:00
Ruud
cc3ebd79e8 Remove extensions from qualities 2013-11-24 23:17:18 +01:00
Ruud
3e035f84b1 Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/helpers/variable.py
	couchpotato/core/media/_base/searcher/main.py
	couchpotato/core/media/movie/searcher/main.py
	couchpotato/core/plugins/quality/main.py
	couchpotato/core/plugins/release/main.py
	couchpotato/core/plugins/renamer/main.py
2013-11-24 23:13:25 +01:00
Joel Kåberg
611c159373 Merge pull request #2356 from fuzeman/tv_searcher
[TV][Searcher] Release Matching and Snatching
2013-10-15 22:16:35 -07:00
Joel Kåberg
db65980ba4 Merge pull request #2354 from nrgaway/tv_xem
Tv xem
2013-10-15 21:51:41 -07:00
Dean Gardiner
180576f2b7 Minor change to ShowSearcher.correctMatch logging 2013-10-16 14:58:53 +13:00
Dean Gardiner
46d4d34da7 Minor cleanup to Searcher and Matcher 2013-10-16 14:58:52 +13:00
Dean Gardiner
3fa21560be Moved 'searcher.create_releases' from Searcher to Release. 2013-10-16 14:58:51 +13:00
Dean Gardiner
b902186389 Cleaned up usage of helper functions 2013-10-16 14:58:50 +13:00
Dean Gardiner
da87e68fad Implemented basic usage of QueryCondenser 2013-10-16 14:58:49 +13:00
Dean Gardiner
f23412ea7e Added qcond (Query Condenser) v0.1.0 library - https://github.com/fuzeman/QueryCondenser 2013-10-16 14:58:48 +13:00
Dean Gardiner
07abf7c83d Updated Caper to version 0.2.2 2013-10-16 14:58:47 +13:00
Dean Gardiner
6259684487 Moved caper matching into a new 'matcher' plugin. 2013-10-16 14:58:47 +13:00
Dean Gardiner
0a0935d635 Fix to Provider getCatId when returning the cet_backup_id 2013-10-16 14:58:46 +13:00
Dean Gardiner
fb5b17005f Cleaned up status.get calls in TV searcher 2013-10-16 14:58:45 +13:00
Dean Gardiner
e3745b5d74 Updated Caper library 2013-10-16 14:58:44 +13:00
Dean Gardiner
8d24d96804 Implemented 'searcher.get_media_searcher_id' in the TV searcher. 2013-10-16 14:58:43 +13:00
Dean Gardiner
529b535d9f Added 'searcher.get_media_searcher_id' event, Cleaned up some 'status.get' calls, Renamed some references of 'nzb' to 'rel'. 2013-10-16 14:58:42 +13:00
Dean Gardiner
0793668e5c Chain result weight now returned from TV searcher correctRelease function. 2013-10-16 14:58:41 +13:00
Dean Gardiner
8d368ecf29 'searcher.correct_release' can now return a float indicating the weight/accuracy which is used to scale the score. Fix to IPT _buildUrl method. 2013-10-16 14:58:40 +13:00
Dean Gardiner
2d2b0c9048 IPT provider now searches in multiple categories. 2013-10-16 14:58:40 +13:00
Dean Gardiner
fb0719d677 TV Searcher now supports xem scene mappings 2013-10-16 14:58:39 +13:00
Dean Gardiner
7ffa5dc7b6 Fixed IPT Show SD cat_ids 2013-10-16 14:58:38 +13:00
Dean Gardiner
32c289fd3d Renamed 'movie' -> 'media' in 'searcher.download' 2013-10-16 14:58:37 +13:00
Dean Gardiner
ff63b8a1c5 Added TV release snatching/downloading 2013-10-16 14:58:36 +13:00
Dean Gardiner
60d8934444 Created 'searcher.try_download_result' event from section in MovieSearcher.single 2013-10-16 14:58:35 +13:00
Jason Mehring
e0aba01866 more tvdb info provider guards 2013-10-15 17:54:06 -04:00
Jason Mehring
1ae498e3c8 Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_xem 2013-10-15 16:25:05 -04:00
Jason Mehring
d1db099f71 grab tvdb fields more defensively 2013-10-15 16:24:49 -04:00
Ruud Burger
f4ef64290d Merge pull request #2352 from fuzeman/tv
[TV] Fixed show searching (broken in search merge)
2013-10-15 12:20:29 -07:00
Dean Gardiner
026151d1a1 Fixed show searching (broken in search merge) 2013-10-15 15:28:35 +13:00
Ruud
70dada8ef6 Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/media/_base/media/main.py
	couchpotato/core/media/_base/searcher/main.py
	couchpotato/core/media/movie/_base/main.py
	couchpotato/core/media/movie/searcher/main.py
2013-10-08 10:02:40 +02:00
Ruud
9ef752f8a3 Rename mediaplugin 2013-10-08 09:22:20 +02:00
Ruud
d265a5bddd Remove refresh from movie media 2013-10-08 08:48:38 +02:00
Ruud
b2b6e3eb33 Cleanup show media 2013-10-08 08:46:04 +02:00
Ruud
2b6c7a8f94 Move media refresh to media plugin 2013-10-08 08:45:45 +02:00
Ruud
6070209d33 Attach shows to searcher 2013-10-07 23:37:17 +02:00
Ruud
fa78d18890 Merge searches 2013-10-07 23:23:46 +02:00
Ruud
40eaf2a96b Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/media/movie/_base/static/search.js
2013-10-07 23:12:59 +02:00
Ruud
73dd0916c0 Merge branch 'tv' of github.com:RuudBurger/CouchPotatoServer into tv 2013-10-03 21:20:24 +02:00
Joel Kåberg
77d32fe16b Merge pull request #2292 from nrgaway/tv_xem
Tv xem
2013-10-03 08:17:26 -07:00
Jason Mehring
7def0944a6 Implemented map_absolute. model was changed to implement. map_names now stores in EpisodeLibrary 2013-10-03 04:30:05 -04:00
Ruud
8782cd77d5 Import cleanup 2013-10-03 08:21:37 +02:00
Ruud
1b59fd9af0 Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/media/_base/searcher/main.py
	couchpotato/core/plugins/renamer/main.py
2013-10-03 08:17:20 +02:00
Joel Kåberg
9dca8a03be Merge pull request #2290 from nrgaway/tv_xem
Tv xem
2013-10-02 05:11:10 -07:00
Jason Mehring
132f4882e5 Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_xem 2013-10-02 01:29:55 -04:00
Jason Mehring
9e32a38288 fixed bug that was not storing xem maps in EpisodeLibrary.info 2013-10-02 01:29:21 -04:00
Joel Kåberg
c1b13cd076 Merge pull request #2284 from fuzeman/tv_searcher
[TV][Searcher] TV searching, parsing and Release creation
2013-10-01 03:29:51 -07:00
Dean Gardiner
820588aa5f Created 'searcher.create_releases' event to replace some shared functionality, releases are now created for TV search results. 2013-09-30 23:08:32 +13:00
Dean Gardiner
8fbf050510 Created 'searcher.search' event to replace some shared functionality, Fixed an issue in Release.download when snatching movies. 2013-09-30 22:49:54 +13:00
Dean Gardiner
dd5ae3c4ee Working TV correctRelease function with quality, identifier and show title checking. 2013-09-30 22:03:21 +13:00
Dean Gardiner
ab51707607 Added Caper (0.2.0-master) and Logr (0.2.1) libraries 2013-09-30 18:40:27 +13:00
Dean Gardiner
8acdc56df1 Added the start of the ShowSearcher correctRelease function 2013-09-30 14:53:33 +13:00
Dean Gardiner
d345a05b3c Switched IPTorrents provider to the MultiProvider layout, few fixes to provider base for MultiProvider 2013-09-30 14:53:32 +13:00
Dean Gardiner
5f427ec6ea Moved required/ignored word checking from 'correctMovie' into 'searcher.correct_words' event, Renamed 'movie.searcher.correct_movie' to 'searcher.correct_release' 2013-09-30 14:53:32 +13:00
Dean Gardiner
a95c030885 Fix for discovering the cat_ids structure when the 'ids' are of str type. 2013-09-30 14:49:42 +13:00
Dean Gardiner
bef6a74dfe Minor cleanup to getSearchTitle 2013-09-30 14:49:41 +13:00
Dean Gardiner
01da470c21 Few changes to getSearchTitle in case a title isn't found, Added check to ensure enough media was returned from _lookupMedia 2013-09-30 14:49:40 +13:00
Dean Gardiner
5fdf4d9085 Extended providers to support multiple media types
- 'cat_ids' now support media type groups
  - 'type' extended to allow a list of support media types
  - Added 'searcher.get_search_title' to return a title for media to be used in searches.
2013-09-30 14:49:40 +13:00
Dean Gardiner
bc51e263e1 Switched back to a single search method 'show.searcher.single' 2013-09-30 14:47:51 +13:00
Dean Gardiner
4c527f0931 Added 'show.refresh' API method and the base for season and episode searching. 2013-09-30 14:47:50 +13:00
Ruud
e9fc528a0f movie_id > media_id 2013-09-23 22:23:45 +02:00
Ruud
c9ba3c804e Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/plugins/dashboard/main.py
	couchpotato/core/plugins/renamer/main.py
	couchpotato/core/providers/torrent/sceneaccess/main.py
2013-09-23 22:14:06 +02:00
Joel Kåberg
ee9fe347c7 Merge pull request #2155 from nrgaway/tv_xem
Tv xem
2013-09-16 12:39:15 -07:00
Jason Mehring
515aafe112 bug fixes for add show 2013-09-13 22:02:22 -04:00
Jason Mehring
314016e1fa (WIP) Started intergrating xem 2013-09-13 03:28:03 -04:00
Jason Mehring
906a54ef09 Finished creating xem info provider 2013-09-13 03:11:51 -04:00
Jason Mehring
ec2facd056 Fix reference to Movie, its now Media 2013-09-13 03:10:22 -04:00
Jason Mehring
ddbfef575f Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_tvdb 2013-09-12 01:30:57 -04:00
Jason Mehring
8dd7a4771c partially implemented xem info provider (wip) 2013-09-12 01:30:38 -04:00
Jason Mehring
49ba1f1acd Reworked code to allow better intergration of other info providers. Initial prep for xem mapping 2013-09-12 01:00:14 -04:00
Ruud
c4d661535c Movie > Media 2013-09-11 23:25:51 +02:00
Ruud
bd52ab7ab1 Movie > Media 2013-09-11 23:18:31 +02:00
Ruud
cce0a8ec62 Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/media/movie/_base/main.py
	couchpotato/core/media/movie/library/movie/main.py
	couchpotato/core/plugins/dashboard/main.py
	couchpotato/core/plugins/profile/main.py
	couchpotato/core/plugins/release/main.py
	couchpotato/core/plugins/suggestion/main.py
2013-09-11 23:13:28 +02:00
Joel Kåberg
d02e62f89f Merge pull request #2139 from nrgaway/tv_tvdb
Tv tvdb
2013-09-11 07:52:42 -07:00
Jason Mehring
e180addc3c added posters for seasons 2013-09-11 00:36:12 -04:00
Jason Mehring
a37a4a8cd4 thetvdb, add alternate titles if they exist 2013-09-10 23:45:15 -04:00
Jason Mehring
8328c18728 set cache directory for thetvdb_api 2013-09-10 22:08:17 -04:00
Jason Mehring
7ae07d6c15 Opps, remove debug code for language 2013-09-10 21:58:47 -04:00
Jason Mehring
770bcf5bc6 Added ability to search thetvdb by language 2013-09-10 21:51:38 -04:00
Jason Mehring
7bd6a295d8 return False on fail. Everything caches now 2013-09-10 21:06:44 -04:00
Jason Mehring
4063761313 Changed model to accept Unicode value for airs_time and add last_updated field for episode. Now stores both as well as airs_daysofweek 2013-09-10 21:04:44 -04:00
Jason Mehring
d62b346a74 Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_tvdb 2013-09-06 13:16:38 -04:00
Ruud
155732ab1a Rollback type remove 2013-09-02 00:13:05 +02:00
Ruud
b3713b7ae5 Merge branch 'refs/heads/develop' into tv 2013-09-02 00:09:16 +02:00
Jason Mehring
19d026756c Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_tvdb 2013-08-24 21:11:53 -04:00
Ruud
3cddd29425 Merge branch 'refs/heads/develop' into tv 2013-08-25 01:15:40 +02:00
Jason Mehring
23bde0b866 Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_tvdb 2013-08-24 15:53:31 -04:00
Jason Mehring
6f895c1805 get apikey from config 2013-08-24 15:37:11 -04:00
Ruud
96089074ce Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/loader.py
2013-08-24 20:22:55 +02:00
Ruud
2ed53df008 Import cleanup 2013-08-24 19:14:09 +02:00
Ruud
060859483a Delete show provider 2013-08-24 18:36:44 +02:00
Ruud
eced476eaf Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/loader.py
	couchpotato/core/providers/info/_modifier/__init__.py
	couchpotato/core/providers/info/_modifier/main.py
	couchpotato/core/providers/movie/_modifier/main.py
	couchpotato/core/providers/show/_modifier/main.py
2013-08-24 18:30:00 +02:00
Ruud
8d5b55a753 Make info modifier multiprovider 2013-08-24 15:30:17 +02:00
Ruud
7296dc54d0 Move thetvdb to info providers 2013-08-24 15:29:57 +02:00
Ruud
e5e9cf7d5f Move info providers to proper folder 2013-08-24 15:20:00 +02:00
Ruud
b106229a78 Merge branch 'refs/heads/develop' into tv 2013-08-24 15:07:21 +02:00
Ruud
73efd5549f Merge branch 'refs/heads/develop' into tv 2013-08-24 14:30:09 +02:00
Joel Kåberg
8139016636 Merge pull request #2061 from nrgaway/tv_loader
fix loader error messages for modules that are selected recursively but ...
2013-08-23 12:51:34 -07:00
Jason Mehring
59c0d0416e fix loader error messages for modules that are selected recursively but are not really modules 2013-08-23 15:32:47 -04:00
Joel Kåberg
cd559ece04 Merge pull request #2058 from nrgaway/tv_refactored
Tv refactored
2013-08-23 07:47:29 -07:00
Joel Kåberg
120a4ad1ed Merge pull request #2057 from nrgaway/tv_development
Completed tvshow model
2013-08-23 07:47:14 -07:00
Jason Mehring
3363e164fd refactored Movie model to Media 2013-08-23 01:37:00 -04:00
Jason Mehring
6d6d5caeb6 Completed tvshow model 2013-08-23 00:53:34 -04:00
Joel Kåberg
21030e7cb4 Merge pull request #2052 from nrgaway/tv_database_2
Added Seasons
2013-08-22 12:40:20 -07:00
Jason Mehring
9b238ba712 Added Seasons. Show is the parent to Seasons and Episodes are the children if Season 2013-08-22 02:47:41 -04:00
Ruud
b3d2d5349b Rename database for TV branch 2013-08-20 23:02:43 +02:00
Ruud Burger
f9bad281de Merge pull request #2038 from nrgaway/tv_database
Tv database
2013-08-20 01:00:29 -07:00
Jason Mehring
72ce919989 Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_database 2013-08-20 02:18:03 -04:00
Jason Mehring
ff782669f6 readded tvdb_api 2013-08-20 02:11:54 -04:00
Jason Mehring
36950993f1 removed tvdb_api since it was missing all files 2013-08-20 02:11:01 -04:00
Jason Mehring
7df93dc1b4 Moved library and refactored to its now location. Modified anything firing libray.add/update/_release date to now fire library.add.movie... 2013-08-20 01:54:47 -04:00
Ruud
a45913eee7 Default to movie type 2013-08-18 13:20:53 +02:00
Ruud
a25eac6c4e Make SceneAccess multiprovider 2013-08-18 11:47:07 +02:00
Ruud
dd0fcf0bc1 Add multiprovider for provider grouping 2013-08-18 11:45:45 +02:00
Ruud
2267235eca Rename type to protocol 2013-08-18 11:44:00 +02:00
Jason Mehring
029cf9ecac New model implemented to work with both Movies and TV Shows as well any future types. Currenly episodes are mapped directly to shows; no seasons yet. Will get around to that soon. This version allows you to add any tv show and it will appear in wanted list, but no searches are written yet :) 2013-08-18 03:28:41 -04:00
Ruud
f4217ecd3d Move registerPlugin to __new__ magic 2013-08-18 00:22:36 +02:00
Jason Mehring
31cd993506 EOD commit (WIP). So close to writing tv objects to database but too tired to finish. Currently storing a show as a movie using imdb metadata. Added another search button beside movie button 2013-08-17 04:15:51 -04:00
Jason Mehring
fb579561de added a --noreloader option flag on startup to prevent CP from auto reloading when in development and debugging mode 2013-08-16 17:10:52 -04:00
Jason Mehring
37eb424827 Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_database 2013-08-16 15:41:37 -04:00
Ruud
4348451692 Merge branch 'refs/heads/develop' into tv
Conflicts:
	couchpotato/core/media/__init__.py
	couchpotato/core/media/_base/searcher/main.py
	couchpotato/core/media/movie/_base/main.py
	couchpotato/core/media/movie/searcher/__init__.py
	couchpotato/core/media/movie/searcher/main.py
2013-08-16 21:07:59 +02:00
Ruud
e93e55a0f7 Searcher conf section 2013-08-16 10:22:43 +02:00
Jason Mehring
bc11f90529 EOD commit (WIP). Commented out schema added yesterday in favour of a more global scheme. Added menu option in GUI to search for tv shows (placed on top of movie one for now). Partially implemented thetvdb provider. Search is working and returns a list of shows for GUI search along with posters. posters still need work. 2013-08-16 02:44:41 -04:00
Jason Mehring
8fcc246f25 Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_database 2013-08-15 20:48:57 -04:00
Ruud
4d7c38d6db Cleanup console log 2013-08-15 23:51:42 +02:00
Ruud
c8d79cde21 Add base for TV media type 2013-08-15 23:47:07 +02:00
Ruud
f4d792079b Give moviesearcher a unique name 2013-08-15 23:46:55 +02:00
Ruud
78ab419cd8 Move movie plugin to media folder 2013-08-15 23:38:14 +02:00
Ruud
3e93983f6e Move movie to new media type folder 2013-08-15 22:22:45 +02:00
Jason Mehring
6a4822cc26 merged upstream changes 2013-08-15 15:08:44 -04:00
Ruud
92b08bb5d5 Merge branch 'refs/heads/develop' into tv 2013-08-15 20:30:48 +02:00
Jason Mehring
e270e09969 EOD commit (WIP). Added partial Show, Episode schema. 2013-08-15 01:15:48 -04:00
Ruud
40cd5218db Change branch to "tv" 2013-08-14 23:24:21 +02:00
356 changed files with 12266 additions and 4686 deletions

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python
from __future__ import print_function
from logging import handlers
from os.path import dirname
import logging
@@ -133,15 +132,14 @@ if __name__ == '__main__':
pass
except SystemExit:
raise
except socket.error as e:
except socket.error as (nr, msg):
# log when socket receives SIGINT, but continue.
# previous code would have skipped over other types of IO errors too.
nr, msg = e
if nr != 4:
try:
l.log.critical(traceback.format_exc())
except:
print(traceback.format_exc())
print traceback.format_exc()
raise
except:
try:
@@ -150,7 +148,7 @@ if __name__ == '__main__':
if l:
l.log.critical(traceback.format_exc())
else:
print(traceback.format_exc())
print traceback.format_exc()
except:
print(traceback.format_exc())
print traceback.format_exc()
raise

View File

@@ -1,231 +0,0 @@
from esky.util import appdir_from_executable #@UnresolvedImport
from threading import Thread
from version import VERSION
from wx.lib.softwareupdate import SoftwareUpdate
import os
import sys
import time
import webbrowser
import wx
# Include proper dirs
if hasattr(sys, 'frozen'):
import libs
base_path = os.path.dirname(os.path.dirname(os.path.abspath(libs.__file__)))
else:
base_path = os.path.dirname(os.path.abspath(__file__))
lib_dir = os.path.join(base_path, 'libs')
sys.path.insert(0, base_path)
sys.path.insert(0, lib_dir)
from couchpotato.environment import Env
class TaskBarIcon(wx.TaskBarIcon):
TBMENU_OPEN = wx.NewId()
TBMENU_SETTINGS = wx.NewId()
TBMENU_EXIT = wx.ID_EXIT
closed = False
menu = False
enabled = False
def __init__(self, frame):
wx.TaskBarIcon.__init__(self)
self.frame = frame
icon = wx.Icon('icon.png', wx.BITMAP_TYPE_PNG)
self.SetIcon(icon)
self.Bind(wx.EVT_TASKBAR_LEFT_UP, self.OnTaskBarClick)
self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTaskBarClick)
self.Bind(wx.EVT_MENU, self.onOpen, id = self.TBMENU_OPEN)
self.Bind(wx.EVT_MENU, self.onSettings, id = self.TBMENU_SETTINGS)
self.Bind(wx.EVT_MENU, self.onTaskBarClose, id = self.TBMENU_EXIT)
def OnTaskBarClick(self, evt):
menu = self.CreatePopupMenu()
self.PopupMenu(menu)
menu.Destroy()
def enable(self):
self.enabled = True
if self.menu:
self.open_menu.Enable(True)
self.setting_menu.Enable(True)
self.open_menu.SetText('Open')
def CreatePopupMenu(self):
if not self.menu:
self.menu = wx.Menu()
self.open_menu = self.menu.Append(self.TBMENU_OPEN, 'Open')
self.setting_menu = self.menu.Append(self.TBMENU_SETTINGS, 'About')
self.exit_menu = self.menu.Append(self.TBMENU_EXIT, 'Quit')
if not self.enabled:
self.open_menu.Enable(False)
self.setting_menu.Enable(False)
self.open_menu.SetText('Loading...')
return self.menu
def onOpen(self, event):
url = self.frame.parent.getSetting('base_url')
webbrowser.open(url)
def onSettings(self, event):
url = self.frame.parent.getSetting('base_url') + 'settings/about/'
webbrowser.open(url)
def onTaskBarClose(self, evt):
if self.closed:
return
self.closed = True
self.RemoveIcon()
wx.CallAfter(self.frame.Close)
def makeIcon(self, img):
if "wxMSW" in wx.PlatformInfo:
img = img.Scale(16, 16)
elif "wxGTK" in wx.PlatformInfo:
img = img.Scale(22, 22)
icon = wx.IconFromBitmap(img.CopyFromBitmap())
return icon
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, None, style = wx.FRAME_NO_TASKBAR)
self.parent = parent
self.tbicon = TaskBarIcon(self)
class WorkerThread(Thread):
def __init__(self, desktop):
Thread.__init__(self)
self.daemon = True
self._desktop = desktop
self.start()
def run(self):
# Get options via arg
from couchpotato.runner import getOptions
args = ['--quiet']
self.options = getOptions(base_path, args)
# Load settings
settings = Env.get('settings')
settings.setFile(self.options.config_file)
# Create data dir if needed
self.data_dir = os.path.expanduser(Env.setting('data_dir'))
if self.data_dir == '':
from couchpotato.core.helpers.variable import getDataDir
self.data_dir = getDataDir()
if not os.path.isdir(self.data_dir):
os.makedirs(self.data_dir)
# Create logging dir
self.log_dir = os.path.join(self.data_dir, 'logs');
if not os.path.isdir(self.log_dir):
os.mkdir(self.log_dir)
try:
from couchpotato.runner import runCouchPotato
runCouchPotato(self.options, base_path, args, data_dir = self.data_dir, log_dir = self.log_dir, Env = Env, desktop = self._desktop)
except:
pass
self._desktop.frame.Close()
class CouchPotatoApp(wx.App, SoftwareUpdate):
settings = {}
events = {}
restart = False
closing = False
def OnInit(self):
# Updater
base_url = 'https://api.couchpota.to/updates/%s'
self.InitUpdates(base_url % VERSION + '/', 'https://couchpota.to/updates/%s' % 'changelog.html',
icon = wx.Icon('icon.png'))
self.frame = MainFrame(self)
self.frame.Bind(wx.EVT_CLOSE, self.onClose)
# CouchPotato thread
self.worker = WorkerThread(self)
return True
def onAppLoad(self):
self.frame.tbicon.enable()
def setSettings(self, settings = {}):
self.settings = settings
def getSetting(self, name):
return self.settings.get(name)
def addEvents(self, events = {}):
for name in events.iterkeys():
self.events[name] = events[name]
def onClose(self, event):
if not self.closing:
self.closing = True
self.frame.tbicon.onTaskBarClose(event)
onClose = self.events.get('onClose')
onClose(event)
def afterShutdown(self, restart = False):
self.frame.Destroy()
self.restart = restart
self.ExitMainLoop()
if __name__ == '__main__':
app = CouchPotatoApp(redirect = False)
app.MainLoop()
time.sleep(1)
if app.restart:
def appexe_from_executable(exepath):
appdir = appdir_from_executable(exepath)
exename = os.path.basename(exepath)
if sys.platform == "darwin":
if os.path.isdir(os.path.join(appdir, "Contents", "MacOS")):
return os.path.join(appdir, "Contents", "MacOS", exename)
return os.path.join(appdir, exename)
exe = appexe_from_executable(sys.executable)
os.chdir(os.path.dirname(exe))
os.execv(exe, [exe] + sys.argv[1:])

View File

@@ -9,12 +9,13 @@ import os
import time
import traceback
log = CPLog(__name__)
views = {}
template_loader = template.Loader(os.path.join(os.path.dirname(__file__), 'templates'))
class BaseHandler(RequestHandler):
def get_current_user(self):
@@ -23,10 +24,9 @@ class BaseHandler(RequestHandler):
if username and password:
return self.get_secure_cookie('user')
else: # Login when no username or password are set
else: # Login when no username or password are set
return True
# Main web handler
class WebHandler(BaseHandler):
@@ -43,13 +43,11 @@ class WebHandler(BaseHandler):
log.error("Failed doing web request '%s': %s", (route, traceback.format_exc()))
self.write({'success': False, 'error': 'Failed returning results'})
def addView(route, func, static = False):
views[route] = func
def get_session():
return Env.getSession()
def get_session(engine = None):
return Env.getSession(engine)
# Web view
@@ -57,10 +55,12 @@ def index():
return template_loader.load('index.html').generate(sep = os.sep, fireEvent = fireEvent, Env = Env)
addView('', index)
# API docs
def apiDocs():
routes = list(api.keys())
routes = []
for route in api.iterkeys():
routes.append(route)
if api_docs.get(''):
del api_docs['']
@@ -70,22 +70,21 @@ def apiDocs():
addView('docs', apiDocs)
# Make non basic auth option to get api key
class KeyHandler(RequestHandler):
def get(self, *args, **kwargs):
api_key = None
api = None
try:
username = Env.setting('username')
password = Env.setting('password')
if (self.get_argument('u') == md5(username) or not username) and (self.get_argument('p') == password or not password):
api_key = Env.setting('api_key')
api = Env.setting('api_key')
self.write({
'success': api_key is not None,
'api_key': api_key
'success': api is not None,
'api_key': api
})
except:
log.error('Failed doing key request: %s', (traceback.format_exc()))
@@ -103,21 +102,20 @@ class LoginHandler(BaseHandler):
def post(self, *args, **kwargs):
api_key = None
api = None
username = Env.setting('username')
password = Env.setting('password')
if (self.get_argument('username') == username or not username) and (md5(self.get_argument('password')) == password or not password):
api_key = Env.setting('api_key')
api = Env.setting('api_key')
if api_key:
if api:
remember_me = tryInt(self.get_argument('remember_me', default = 0))
self.set_secure_cookie('user', api_key, expires_days = 30 if remember_me > 0 else None)
self.set_secure_cookie('user', api, expires_days = 30 if remember_me > 0 else None)
self.redirect(Env.get('web_base'))
class LogoutHandler(BaseHandler):
def get(self, *args, **kwargs):
@@ -138,3 +136,4 @@ def page_not_found(rh):
rh.set_status(404)
rh.write('Wrong API key used')

View File

@@ -20,7 +20,6 @@ api_nonblock = {}
api_docs = {}
api_docs_missing = []
def run_async(func):
@wraps(func)
def async_func(*args, **kwargs):
@@ -30,7 +29,6 @@ def run_async(func):
return async_func
# NonBlock API handler
class NonBlockHandler(RequestHandler):
@@ -63,7 +61,6 @@ class NonBlockHandler(RequestHandler):
self.stopper = None
def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
api_nonblock[route] = func_tuple
@@ -72,7 +69,6 @@ def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
else:
api_docs_missing.append(route)
# Blocking API handler
class ApiHandler(RequestHandler):
@@ -102,12 +98,11 @@ class ApiHandler(RequestHandler):
@run_async
def run_handler(callback):
try:
res = api[route](**kwargs)
callback(res)
result = api[route](**kwargs)
callback(result)
except:
log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
callback({'success': False, 'error': 'Failed returning results'})
result = yield tornado.gen.Task(run_handler)
# Check JSONP callback
@@ -127,7 +122,6 @@ class ApiHandler(RequestHandler):
api_locks[route].release()
def addApiView(route, func, static = False, docs = None, **kwargs):
if static: func(route)

View File

@@ -1,7 +1,6 @@
from .main import Core
from uuid import uuid4
def start():
return Core()

View File

@@ -117,7 +117,7 @@ class Core(Plugin):
if len(still_running) == 0:
break
elif starttime < time.time() - 30: # Always force break after 30s wait
elif starttime < time.time() - 30: # Always force break after 30s wait
break
running = list(set(still_running) - set(self.ignore_restart))

View File

@@ -1,6 +1,5 @@
from .main import ClientScript
def start():
return ClientScript()

View File

@@ -49,14 +49,13 @@ class ClientScript(Plugin):
'scripts/page/settings.js',
'scripts/page/about.js',
'scripts/page/manage.js',
'scripts/misc/downloaders.js',
],
}
urls = {'style': {}, 'script': {}}
minified = {'style': {}, 'script': {}}
paths = {'style': {}, 'script': {}}
urls = {'style': {}, 'script': {}, }
minified = {'style': {}, 'script': {}, }
paths = {'style': {}, 'script': {}, }
comment = {
'style': '/*** %s:%d ***/\n',
'script': '// %s:%d\n'

View File

@@ -1,6 +1,5 @@
from .main import Desktop
def start():
return Desktop()

View File

@@ -1,6 +1,5 @@
from .main import Scheduler
def start():
return Scheduler()

View File

@@ -17,7 +17,6 @@ class Scheduler(Plugin):
addEvent('schedule.cron', self.cron)
addEvent('schedule.interval', self.interval)
addEvent('schedule.remove', self.remove)
addEvent('schedule.queue', self.queue)
self.sched = Sched(misfire_grace_time = 60)
self.sched.start()
@@ -65,14 +64,3 @@ class Scheduler(Plugin):
'seconds': seconds,
'job': self.sched.add_interval_job(handle, hours = hours, minutes = minutes, seconds = seconds)
}
def queue(self, handlers = None):
if not handlers: handlers = []
for h in handlers:
h()
if self.shuttingDown():
break
return True

View File

@@ -2,7 +2,6 @@ from .main import Updater
from couchpotato.environment import Env
import os
def start():
return Updater()

View File

@@ -15,7 +15,6 @@ import time
import traceback
import version
import zipfile
from six.moves import filter
log = CPLog(__name__)
@@ -33,7 +32,6 @@ class Updater(Plugin):
else:
self.updater = SourceUpdater()
addEvent('app.load', self.logVersion, priority = 10000)
addEvent('app.load', self.setCrons)
addEvent('updater.info', self.info)
@@ -55,16 +53,12 @@ class Updater(Plugin):
addEvent('setting.save.updater.enabled.after', self.setCrons)
def logVersion(self):
info = self.info()
log.info('=== VERSION %s, using %s ===', (info.get('version', {}).get('repr', 'UNKNOWN'), self.updater.getName()))
def setCrons(self):
fireEvent('schedule.remove', 'updater.check', single = True)
if self.isEnabled():
fireEvent('schedule.interval', 'updater.check', self.autoUpdate, hours = 6)
self.autoUpdate() # Check after enabling
self.autoUpdate() # Check after enabling
def autoUpdate(self):
if self.isEnabled() and self.check() and self.conf('automatic') and not self.updater.update_failed:
@@ -152,9 +146,6 @@ class BaseUpdater(Plugin):
'branch': self.branch,
}
def getVersion(self):
pass
def check(self):
pass
@@ -183,6 +174,7 @@ class BaseUpdater(Plugin):
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
class GitUpdater(BaseUpdater):
def __init__(self, git_command):
@@ -209,15 +201,14 @@ class GitUpdater(BaseUpdater):
if not self.version:
try:
output = self.repo.getHead() # Yes, please
output = self.repo.getHead() # Yes, please
log.debug('Git version output: %s', output.hash)
self.version = {
'repr': 'git:(%s:%s % s) %s (%s)' % (self.repo_user, self.repo_name, self.branch, output.hash[:8], datetime.fromtimestamp(output.getDate())),
'hash': output.hash[:8],
'date': output.getDate(),
'type': 'git',
}
except Exception as e:
except Exception, e:
log.error('Failed using GIT updater, running from source, you need to have GIT installed. %s', e)
return 'No GIT'
@@ -240,7 +231,7 @@ class GitUpdater(BaseUpdater):
local = self.repo.getHead()
remote = branch.getHead()
log.debug('Versions, local:%s, remote:%s', (local.hash[:8], remote.hash[:8]))
log.info('Versions, local:%s, remote:%s', (local.hash[:8], remote.hash[:8]))
if local.getDate() < remote.getDate():
self.update_version = {
@@ -253,6 +244,7 @@ class GitUpdater(BaseUpdater):
return False
class SourceUpdater(BaseUpdater):
def __init__(self):
@@ -278,9 +270,9 @@ class SourceUpdater(BaseUpdater):
# Extract
if download_data.get('type') == 'zip':
zip_file = zipfile.ZipFile(destination)
zip_file.extractall(extracted_path)
zip_file.close()
zip = zipfile.ZipFile(destination)
zip.extractall(extracted_path)
zip.close()
else:
tar = tarfile.open(destination)
tar.extractall(path = extracted_path)
@@ -347,12 +339,13 @@ class SourceUpdater(BaseUpdater):
return True
def removeDir(self, path):
try:
if os.path.isdir(path):
shutil.rmtree(path)
except OSError as inst:
os.chmod(inst.filename, 0o777)
except OSError, inst:
os.chmod(inst.filename, 0777)
self.removeDir(path)
def getVersion(self):
@@ -366,8 +359,7 @@ class SourceUpdater(BaseUpdater):
log.debug('Source version output: %s', output)
self.version = output
self.version['type'] = 'source'
self.version['repr'] = 'source:(%s:%s % s) %s (%s)' % (self.repo_user, self.repo_name, self.branch, output.get('hash', '')[:8], datetime.fromtimestamp(output.get('date', 0)))
except Exception as e:
except Exception, e:
log.error('Failed using source updater. %s', e)
return {}
@@ -397,7 +389,7 @@ class SourceUpdater(BaseUpdater):
return {
'hash': commit['sha'],
'date': int(time.mktime(parse(commit['commit']['committer']['date']).timetuple())),
'date': int(time.mktime(parse(commit['commit']['committer']['date']).timetuple())),
}
except:
log.error('Failed getting latest request from github: %s', traceback.format_exc())
@@ -442,7 +434,7 @@ class DesktopUpdater(BaseUpdater):
if latest and latest != current_version.get('hash'):
self.update_version = {
'hash': latest,
'date': None,
'date': None,
'changelog': self.desktop._changelogURL,
}
@@ -454,7 +446,6 @@ class DesktopUpdater(BaseUpdater):
def getVersion(self):
return {
'repr': 'desktop: %s' % self.desktop._esky.active_version,
'hash': self.desktop._esky.active_version,
'date': None,
'type': 'desktop',

View File

@@ -24,7 +24,7 @@ var UpdaterBase = new Class({
self.doUpdate();
else {
App.unBlockPage();
App.trigger('message', ['No updates available']);
App.on('message', 'No updates available');
}
}
})

View File

@@ -1,5 +1,4 @@
from base64 import b32decode, b16encode
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.variable import mergeDicts
from couchpotato.core.logger import CPLog
@@ -43,7 +42,6 @@ class Downloader(Provider):
addEvent('download.remove_failed', self._removeFailed)
addEvent('download.pause', self._pause)
addEvent('download.process_complete', self._processComplete)
addApiView('download.%s.test' % self.getName().lower(), self._test)
def getEnabledProtocol(self):
for download_protocol in self.protocol:
@@ -160,15 +158,6 @@ class Downloader(Provider):
(d_manual and manual or d_manual is False) and \
(not data or self.isCorrectProtocol(data.get('protocol')))
def _test(self):
t = self.test()
if isinstance(t, tuple):
return {'success': t[0], 'msg': t[1]}
return {'success': t}
def test(self):
return False
def _pause(self, release_download, pause = True):
if self.isDisabled(manual = True, data = {}):
return

View File

@@ -1,7 +1,6 @@
from .main import Blackhole
from couchpotato.core.helpers.variable import getDownloadDir
def start():
return Blackhole()

View File

@@ -1,6 +1,5 @@
from __future__ import with_statement
from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.helpers.encoding import sp
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
import os
@@ -68,20 +67,6 @@ class Blackhole(Downloader):
return False
def test(self):
directory = self.conf('directory')
if directory and os.path.isdir(directory):
test_file = sp(os.path.join(directory, 'couchpotato_test.txt'))
# Check if folder is writable
self.createFile(test_file, 'This is a test file')
if os.path.isfile(test_file):
os.remove(test_file)
return True
return False
def getEnabledProtocol(self):
if self.conf('use_for') == 'both':
return super(Blackhole, self).getEnabledProtocol()

View File

@@ -1,6 +1,5 @@
from .main import Deluge
def start():
return Deluge()

View File

@@ -2,7 +2,7 @@ from base64 import b64encode, b16encode, b32decode
from bencode import bencode as benc, bdecode
from couchpotato.core.downloaders.base import Downloader, ReleaseDownloadList
from couchpotato.core.helpers.encoding import isInt, sp
from couchpotato.core.helpers.variable import tryFloat, cleanHost
from couchpotato.core.helpers.variable import tryFloat
from couchpotato.core.logger import CPLog
from datetime import timedelta
from hashlib import sha1
@@ -20,14 +20,14 @@ class Deluge(Downloader):
log = CPLog(__name__)
drpc = None
def connect(self, reconnect = False):
def connect(self):
# Load host from config and split out port.
host = cleanHost(self.conf('host'), protocol = False).split(':')
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
if not self.drpc or reconnect:
if not self.drpc:
self.drpc = DelugeRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
return self.drpc
@@ -86,11 +86,6 @@ class Deluge(Downloader):
log.info('Torrent sent to Deluge successfully.')
return self.downloadReturnId(remote_torrent)
def test(self):
if self.connect(True) and self.drpc.test():
return True
return False
def getAllDownloadStatus(self, ids):
log.debug('Checking Deluge download status.')
@@ -108,13 +103,8 @@ class Deluge(Downloader):
for torrent_id in queue:
torrent = queue[torrent_id]
if not 'hash' in torrent:
# When given a list of ids, deluge will return an empty item for a non-existant torrent.
continue
log.debug('name=%s / id=%s / save_path=%s / move_on_completed=%s / move_completed_path=%s / hash=%s / progress=%s / state=%s / eta=%s / ratio=%s / stop_ratio=%s / is_seed=%s / is_finished=%s / paused=%s', (torrent['name'], torrent['hash'], torrent['save_path'], torrent['move_on_completed'], torrent['move_completed_path'], torrent['hash'], torrent['progress'], torrent['state'], torrent['eta'], torrent['ratio'], torrent['stop_ratio'], torrent['is_seed'], torrent['is_finished'], torrent['paused']))
log.debug('name=%s / id=%s / save_path=%s / move_completed_path=%s / hash=%s / progress=%s / state=%s / eta=%s / ratio=%s / stop_ratio=%s / is_seed=%s / is_finished=%s / paused=%s', (torrent['name'], torrent['hash'], torrent['save_path'], torrent['move_completed_path'], torrent['hash'], torrent['progress'], torrent['state'], torrent['eta'], torrent['ratio'], torrent['stop_ratio'], torrent['is_seed'], torrent['is_finished'], torrent['paused']))
# Deluge has no easy way to work out if a torrent is stalled or failing.
#status = 'failed'
status = 'busy'
@@ -130,11 +120,11 @@ class Deluge(Downloader):
download_dir = sp(torrent['save_path'])
if torrent['move_on_completed']:
download_dir = torrent['move_completed_path']
torrent_files = []
for file_item in torrent['files']:
torrent_files.append(sp(os.path.join(download_dir, file_item['path'])))
release_downloads.append({
'id': torrent['hash'],
'name': torrent['name'],
@@ -162,7 +152,6 @@ class Deluge(Downloader):
log.debug('Requesting Deluge to remove the torrent %s%s.', (release_download['name'], ' and cleanup the downloaded files' if delete_files else ''))
return self.drpc.remove_torrent(release_download['id'], remove_local_data = delete_files)
class DelugeRPC(object):
host = 'localhost'
@@ -183,13 +172,6 @@ class DelugeRPC(object):
self.client = DelugeClient()
self.client.connect(self.host, int(self.port), self.username, self.password)
def test(self):
try:
self.connect()
except:
return False
return True
def add_torrent_magnet(self, torrent, options):
torrent_id = False
try:
@@ -200,7 +182,7 @@ class DelugeRPC(object):
if torrent_id and options['label']:
self.client.label.set_torrent(torrent_id, options['label']).get()
except Exception as err:
except Exception, err:
log.error('Failed to add torrent magnet %s: %s %s', (torrent, err, traceback.format_exc()))
finally:
if self.client:
@@ -218,7 +200,7 @@ class DelugeRPC(object):
if torrent_id and options['label']:
self.client.label.set_torrent(torrent_id, options['label']).get()
except Exception as err:
except Exception, err:
log.error('Failed to add torrent file %s: %s %s', (filename, err, traceback.format_exc()))
finally:
if self.client:
@@ -230,8 +212,8 @@ class DelugeRPC(object):
ret = False
try:
self.connect()
ret = self.client.core.get_torrents_status({'id': ids}, ('name', 'hash', 'save_path', 'move_completed_path', 'progress', 'state', 'eta', 'ratio', 'stop_ratio', 'is_seed', 'is_finished', 'paused', 'move_on_completed', 'files')).get()
except Exception as err:
ret = self.client.core.get_torrents_status({'id': ids}, {}).get()
except Exception, err:
log.error('Failed to get all torrents: %s %s', (err, traceback.format_exc()))
finally:
if self.client:
@@ -242,7 +224,7 @@ class DelugeRPC(object):
try:
self.connect()
self.client.core.pause_torrent(torrent_ids).get()
except Exception as err:
except Exception, err:
log.error('Failed to pause torrent: %s %s', (err, traceback.format_exc()))
finally:
if self.client:
@@ -252,7 +234,7 @@ class DelugeRPC(object):
try:
self.connect()
self.client.core.resume_torrent(torrent_ids).get()
except Exception as err:
except Exception, err:
log.error('Failed to resume torrent: %s %s', (err, traceback.format_exc()))
finally:
if self.client:
@@ -263,7 +245,7 @@ class DelugeRPC(object):
try:
self.connect()
ret = self.client.core.remove_torrent(torrent_id, remove_local_data).get()
except Exception as err:
except Exception, err:
log.error('Failed to remove torrent: %s %s', (err, traceback.format_exc()))
finally:
if self.client:

View File

@@ -1,6 +1,5 @@
from .main import NZBGet
def start():
return NZBGet()

View File

@@ -1,7 +1,7 @@
from base64 import standard_b64encode
from couchpotato.core.downloaders.base import Downloader, ReleaseDownloadList
from couchpotato.core.helpers.encoding import ss, sp
from couchpotato.core.helpers.variable import tryInt, md5, cleanHost
from couchpotato.core.helpers.variable import tryInt, md5
from couchpotato.core.logger import CPLog
from datetime import timedelta
import re
@@ -16,7 +16,8 @@ log = CPLog(__name__)
class NZBGet(Downloader):
protocol = ['nzb']
rpc = 'xmlrpc'
url = '%(protocol)s://%(username)s:%(password)s@%(host)s/xmlrpc'
def download(self, data = None, media = None, filedata = None):
if not media: media = {}
@@ -28,10 +29,10 @@ class NZBGet(Downloader):
log.info('Sending "%s" to NZBGet.', data.get('name'))
url = self.url % {'protocol': 'https' if self.conf('ssl') else 'http', 'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
nzb_name = ss('%s.nzb' % self.createNzbName(data, media))
rpc = self.getRPC()
rpc = xmlrpclib.ServerProxy(url)
try:
if rpc.writelog('INFO', 'CouchPotato connected to drop off %s.' % nzb_name):
log.debug('Successfully connected to NZBGet')
@@ -40,7 +41,7 @@ class NZBGet(Downloader):
except socket.error:
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
return False
except xmlrpclib.ProtocolError as e:
except xmlrpclib.ProtocolError, e:
if e.errcode == 401:
log.error('Password is incorrect.')
else:
@@ -54,7 +55,7 @@ class NZBGet(Downloader):
if xml_response:
log.info('NZB sent successfully to NZBGet')
nzb_id = md5(data['url']) # about as unique as they come ;)
nzb_id = md5(data['url']) # about as unique as they come ;)
couchpotato_id = "couchpotato=" + nzb_id
groups = rpc.listgroups()
file_id = [item['LastID'] for item in groups if item['NZBFilename'] == nzb_name]
@@ -66,32 +67,13 @@ class NZBGet(Downloader):
log.error('NZBGet could not add %s to the queue.', nzb_name)
return False
def test(self):
rpc = self.getRPC()
try:
if rpc.writelog('INFO', 'CouchPotato connected to test connection'):
log.debug('Successfully connected to NZBGet')
else:
log.info('Successfully connected to NZBGet, but unable to send a message')
except socket.error:
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
return False
except xmlrpclib.ProtocolError as e:
if e.errcode == 401:
log.error('Password is incorrect.')
else:
log.error('Protocol Error: %s', e)
return False
return True
def getAllDownloadStatus(self, ids):
log.debug('Checking NZBGet download status.')
rpc = self.getRPC()
url = self.url % {'protocol': 'https' if self.conf('ssl') else 'http', 'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
rpc = xmlrpclib.ServerProxy(url)
try:
if rpc.writelog('INFO', 'CouchPotato connected to check status'):
log.debug('Successfully connected to NZBGet')
@@ -100,7 +82,7 @@ class NZBGet(Downloader):
except socket.error:
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
return []
except xmlrpclib.ProtocolError as e:
except xmlrpclib.ProtocolError, e:
if e.errcode == 401:
log.error('Password is incorrect.')
else:
@@ -133,7 +115,7 @@ class NZBGet(Downloader):
timeleft = str(timedelta(seconds = nzb['RemainingSizeMB'] / status['DownloadRate'] * 2 ^ 20))
except:
pass
release_downloads.append({
'id': nzb_id,
'name': nzb['NZBFilename'],
@@ -175,8 +157,9 @@ class NZBGet(Downloader):
log.info('%s failed downloading, deleting...', release_download['name'])
rpc = self.getRPC()
url = self.url % {'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
rpc = xmlrpclib.ServerProxy(url)
try:
if rpc.writelog('INFO', 'CouchPotato connected to delete some history'):
log.debug('Successfully connected to NZBGet')
@@ -185,7 +168,7 @@ class NZBGet(Downloader):
except socket.error:
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
return False
except xmlrpclib.ProtocolError as e:
except xmlrpclib.ProtocolError, e:
if e.errcode == 401:
log.error('Password is incorrect.')
else:
@@ -210,7 +193,3 @@ class NZBGet(Downloader):
return False
return True
def getRPC(self):
url = cleanHost(host = self.conf('host'), ssl = self.conf('ssl'), username = self.conf('username'), password = self.conf('password')) + self.rpc
return xmlrpclib.ServerProxy(url)

View File

@@ -1,6 +1,5 @@
from .main import NZBVortex
def start():
return NZBVortex()
@@ -23,15 +22,7 @@ config = [{
},
{
'name': 'host',
'default': 'localhost:4321',
'description': 'Hostname with port. Usually <strong>localhost:4321</strong>',
},
{
'name': 'ssl',
'default': 1,
'type': 'bool',
'advanced': True,
'description': 'Use HyperText Transfer Protocol Secure, or <strong>https</strong>',
'default': 'https://localhost:4321',
},
{
'name': 'api_key',

View File

@@ -36,20 +36,12 @@ class NZBVortex(Downloader):
time.sleep(10)
raw_statuses = self.call('nzb')
nzb_id = [nzb['id'] for nzb in raw_statuses.get('nzbs', []) if os.path.basename(nzb['nzbFileName']) == nzb_filename][0]
nzb_id = [nzb['id'] for nzb in raw_statuses.get('nzbs', []) if os.path.basename(item['nzbFileName']) == nzb_filename][0]
return self.downloadReturnId(nzb_id)
except:
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
return False
def test(self):
try:
login_result = self.login()
except:
return False
return login_result
def getAllDownloadStatus(self, ids):
raw_statuses = self.call('nzb')
@@ -64,13 +56,13 @@ class NZBVortex(Downloader):
status = 'completed'
elif nzb['state'] in [21, 22, 24]:
status = 'failed'
release_downloads.append({
'id': nzb['id'],
'name': nzb['uiTitle'],
'status': status,
'original_status': nzb['state'],
'timeleft': -1,
'timeleft':-1,
'folder': sp(nzb['destinationPath']),
})
@@ -110,6 +102,7 @@ class NZBVortex(Downloader):
log.error('Login failed, please check you api-key')
return False
def call(self, call, parameters = None, repeat = False, auth = True, *args, **kwargs):
# Login first
@@ -123,14 +116,14 @@ class NZBVortex(Downloader):
params = tryUrlencode(parameters)
url = cleanHost(self.conf('host'), ssl = self.conf('ssl')) + 'api/' + call
url = cleanHost(self.conf('host')) + 'api/' + call
try:
data = self.urlopen('%s?%s' % (url, params), *args, **kwargs)
if data:
return json.loads(data)
except URLError as e:
except URLError, e:
if hasattr(e, 'code') and e.code == 403:
# Try login and do again
if not repeat:
@@ -152,7 +145,7 @@ class NZBVortex(Downloader):
try:
data = self.urlopen(url, show_error = False)
self.api_level = float(json.loads(data).get('apilevel'))
except URLError as e:
except URLError, e:
if hasattr(e, 'code') and e.code == 403:
log.error('This version of NZBVortex isn\'t supported. Please update to 2.8.6 or higher')
else:
@@ -182,7 +175,6 @@ class HTTPSConnection(httplib.HTTPSConnection):
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version = ssl.PROTOCOL_TLSv1)
class HTTPSHandler(urllib2.HTTPSHandler):
def https_open(self, req):
return self.do_open(HTTPSConnection, req)

View File

@@ -1,6 +1,5 @@
from .main import Pneumatic
def start():
return Pneumatic()

View File

@@ -1,6 +1,5 @@
from __future__ import with_statement
from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.helpers.encoding import sp
from couchpotato.core.logger import CPLog
import os
import traceback
@@ -27,26 +26,26 @@ class Pneumatic(Downloader):
log.error('No nzb available!')
return False
full_path = os.path.join(directory, self.createFileName(data, filedata, media))
fullPath = os.path.join(directory, self.createFileName(data, filedata, media))
try:
if not os.path.isfile(full_path):
log.info('Downloading %s to %s.', (data.get('protocol'), full_path))
with open(full_path, 'wb') as f:
if not os.path.isfile(fullPath):
log.info('Downloading %s to %s.', (data.get('protocol'), fullPath))
with open(fullPath, 'wb') as f:
f.write(filedata)
nzb_name = self.createNzbName(data, media)
strm_path = os.path.join(directory, nzb_name)
strm_file = open(strm_path + '.strm', 'wb')
strmContent = self.strm_syntax % (full_path, nzb_name)
strmContent = self.strm_syntax % (fullPath, nzb_name)
strm_file.write(strmContent)
strm_file.close()
return self.downloadReturnId('')
else:
log.info('File %s already exists.', full_path)
log.info('File %s already exists.', fullPath)
return self.downloadReturnId('')
except:
@@ -57,17 +56,3 @@ class Pneumatic(Downloader):
log.info('Failed to download file %s: %s', (data.get('name'), traceback.format_exc()))
return False
return False
def test(self):
directory = self.conf('directory')
if directory and os.path.isdir(directory):
test_file = sp(os.path.join(directory, 'couchpotato_test.txt'))
# Check if folder is writable
self.createFile(test_file, 'This is a test file')
if os.path.isfile(test_file):
os.remove(test_file)
return True
return False

View File

@@ -1,6 +1,5 @@
from .main import rTorrent
def start():
return rTorrent()
@@ -21,32 +20,11 @@ config = [{
'type': 'enabler',
'radio_group': 'torrent',
},
# @RuudBurger: How do I migrate this?
# {
# 'name': 'url',
# 'default': 'http://localhost:80/RPC2',
# 'description': 'XML-RPC Endpoint URI. Usually <strong>scgi://localhost:5000</strong> '
# 'or <strong>http://localhost:80/RPC2</strong>'
# },
{
'name': 'host',
'default': 'localhost:80',
'description': 'RPC Communication URI. Usually <strong>scgi://localhost:5000</strong>, '
'<strong>httprpc://localhost/rutorrent</strong> or <strong>localhost:80</strong>'
},
{
'name': 'ssl',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Use HyperText Transfer Protocol Secure, or <strong>https</strong>',
},
{
'name': 'rpc_url',
'type': 'string',
'default': 'RPC2',
'advanced': True,
'description': 'Change if your RPC mount is at a different path.',
'name': 'url',
'default': 'http://localhost:80/RPC2',
'description': 'XML-RPC Endpoint URI. Usually <strong>scgi://localhost:5000</strong> '
'or <strong>http://localhost:80/RPC2</strong>'
},
{
'name': 'username',

View File

@@ -1,15 +1,12 @@
from couchpotato.core.downloaders.base import Downloader, ReleaseDownloadList
from couchpotato.core.event import fireEvent, addEvent
from couchpotato.core.helpers.encoding import sp
from couchpotato.core.helpers.variable import cleanHost, splitString
from couchpotato.core.logger import CPLog
from base64 import b16encode, b32decode
from bencode import bencode, bdecode
from couchpotato.core.downloaders.base import Downloader, ReleaseDownloadList
from couchpotato.core.helpers.encoding import sp
from couchpotato.core.logger import CPLog
from datetime import timedelta
from hashlib import sha1
from rtorrent import RTorrent
from rtorrent.err import MethodError
from urlparse import urlparse
import os
log = CPLog(__name__)
@@ -19,75 +16,29 @@ class rTorrent(Downloader):
protocol = ['torrent', 'torrent_magnet']
rt = None
error_msg = ''
# Migration url to host options
def __init__(self):
super(rTorrent, self).__init__()
addEvent('app.load', self.migrate)
addEvent('setting.save.rtorrent.*.after', self.settingsChanged)
def migrate(self):
url = self.conf('url')
if url:
host_split = splitString(url.split('://')[-1], split_on = '/')
self.conf('ssl', value = url.startswith('https'))
self.conf('host', value = host_split[0].strip())
self.conf('rpc_url', value = '/'.join(host_split[1:]))
self.deleteConf('url')
def settingsChanged(self):
# Reset active connection if settings have changed
if self.rt:
log.debug('Settings have changed, closing active connection')
self.rt = None
return True
def connect(self, reconnect = False):
def connect(self):
# Already connected?
if not reconnect and self.rt is not None:
if self.rt is not None:
return self.rt
url = cleanHost(self.conf('host'), protocol = True, ssl = self.conf('ssl'))
parsed = urlparse(url)
# rpc_url is only used on http/https scgi pass-through
if parsed.scheme in ['http', 'https']:
url += self.conf('rpc_url')
# Ensure url is set
if not self.conf('url'):
log.error('Config properties are not filled in correctly, url is missing.')
return False
if self.conf('username') and self.conf('password'):
self.rt = RTorrent(
url,
self.conf('url'),
self.conf('username'),
self.conf('password')
)
else:
self.rt = RTorrent(url)
self.error_msg = ''
try:
self.rt._verify_conn()
except AssertionError as e:
self.error_msg = e.message
self.rt = None
self.rt = RTorrent(self.conf('url'))
return self.rt
def test(self):
if self.connect(True):
return True
if self.error_msg:
return False, 'Connection failed: ' + self.error_msg
return False
def updateProviderGroup(self, name, data):
def _update_provider_group(self, name, data):
if data.get('seed_time'):
log.info('seeding time ignored, not supported')
@@ -119,7 +70,7 @@ class rTorrent(Downloader):
# Reset group action and disable it
group.set_command()
group.disable()
except MethodError as err:
except MethodError, err:
log.error('Unable to set group options: %s', err.msg)
return False
@@ -136,13 +87,14 @@ class rTorrent(Downloader):
return False
group_name = 'cp_' + data.get('provider').lower()
if not self.updateProviderGroup(group_name, data):
if not self._update_provider_group(group_name, data):
return False
torrent_params = {}
if self.conf('label'):
torrent_params['label'] = self.conf('label')
if not filedata and data.get('protocol') == 'torrent':
log.error('Failed sending torrent, no data')
return False
@@ -166,7 +118,7 @@ class rTorrent(Downloader):
# Send request to rTorrent
try:
# Send torrent to rTorrent
torrent = self.rt.load_torrent(filedata, verify_retries=10)
torrent = self.rt.load_torrent(filedata)
if not torrent:
log.error('Unable to find the torrent, did it fail to load?')
@@ -187,25 +139,10 @@ class rTorrent(Downloader):
torrent.start()
return self.downloadReturnId(torrent_hash)
except Exception as err:
except Exception, err:
log.error('Failed to send torrent to rTorrent: %s', err)
return False
def getTorrentStatus(self, torrent):
if torrent.hashing or torrent.hash_checking or torrent.message:
return 'busy'
if not torrent.complete:
return 'busy'
if not torrent.open:
return 'completed'
if torrent.state and torrent.active:
return 'seeding'
return 'busy'
def getAllDownloadStatus(self, ids):
log.debug('Checking rTorrent download status.')
@@ -219,21 +156,21 @@ class rTorrent(Downloader):
for torrent in torrents:
if torrent.info_hash in ids:
torrent_directory = os.path.normpath(torrent.directory)
torrent_files = []
for file in torrent.get_files():
if not os.path.normpath(file.path).startswith(torrent_directory):
file_path = os.path.join(torrent_directory, file.path.lstrip('/'))
for file_item in torrent.get_files():
torrent_files.append(sp(os.path.join(torrent.directory, file_item.path)))
status = 'busy'
if torrent.complete:
if torrent.active:
status = 'seeding'
else:
file_path = file.path
torrent_files.append(sp(file_path))
status = 'completed'
release_downloads.append({
'id': torrent.info_hash,
'name': torrent.name,
'status': self.getTorrentStatus(torrent),
'status': status,
'seed_ratio': torrent.ratio,
'original_status': torrent.state,
'timeleft': str(timedelta(seconds = float(torrent.left_bytes) / torrent.down_rate)) if torrent.down_rate > 0 else -1,
@@ -243,7 +180,7 @@ class rTorrent(Downloader):
return release_downloads
except Exception as err:
except Exception, err:
log.error('Failed to get status from rTorrent: %s', err)
return []

View File

@@ -1,6 +1,5 @@
from .main import Sabnzbd
def start():
return Sabnzbd()
@@ -25,13 +24,6 @@ config = [{
'name': 'host',
'default': 'localhost:8080',
},
{
'name': 'ssl',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Use HyperText Transfer Protocol Secure, or <strong>https</strong>',
},
{
'name': 'api_key',
'label': 'Api Key',

View File

@@ -64,26 +64,6 @@ class Sabnzbd(Downloader):
log.error('Error getting data from SABNZBd: %s', sab_data)
return False
def test(self):
try:
sab_data = self.call({
'mode': 'version',
})
v = sab_data.split('.')
if int(v[0]) == 0 and int(v[1]) < 7:
return False, 'Your Sabnzbd client is too old, please update to newest version.'
# the version check will work even with wrong api key, so we need the next check as well
sab_data = self.call({
'mode': 'qstatus',
})
if not sab_data:
return False
except:
return False
return True
def getAllDownloadStatus(self, ids):
log.debug('Checking SABnzbd download status.')
@@ -115,7 +95,7 @@ class Sabnzbd(Downloader):
status = 'busy'
if 'ENCRYPTED / ' in nzb['filename']:
status = 'failed'
release_downloads.append({
'id': nzb['nzo_id'],
'name': nzb['filename'],
@@ -132,7 +112,7 @@ class Sabnzbd(Downloader):
status = 'failed'
elif nzb['status'] == 'Completed':
status = 'completed'
release_downloads.append({
'id': nzb['nzo_id'],
'name': nzb['name'],
@@ -185,9 +165,9 @@ class Sabnzbd(Downloader):
def call(self, request_params, use_json = True, **kwargs):
url = cleanHost(self.conf('host'), ssl = self.conf('ssl')) + 'api?' + tryUrlencode(mergeDicts(request_params, {
'apikey': self.conf('api_key'),
'output': 'json'
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(request_params, {
'apikey': self.conf('api_key'),
'output': 'json'
}))
data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}, **kwargs)

View File

@@ -1,6 +1,5 @@
from .main import Synology
def start():
return Synology()

View File

@@ -1,6 +1,5 @@
from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.helpers.encoding import isInt
from couchpotato.core.helpers.variable import cleanHost
from couchpotato.core.logger import CPLog
import json
import requests
@@ -22,7 +21,7 @@ class Synology(Downloader):
log.error('Sending "%s" (%s) to Synology.', (data['name'], data['protocol']))
# Load host from config and split out port.
host = cleanHost(self.conf('host'), protocol = False).split(':')
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
@@ -45,16 +44,6 @@ class Synology(Downloader):
finally:
return self.downloadReturnId('') if response else False
def test(self):
host = cleanHost(self.conf('host'), protocol = False).split(':')
try:
srpc = SynologyRPC(host[0], host[1], self.conf('username'), self.conf('password'))
test_result = srpc.test()
except:
return False
return test_result
def getEnabledProtocol(self):
if self.conf('use_for') == 'both':
return super(Synology, self).getEnabledProtocol()
@@ -75,7 +64,6 @@ class Synology(Downloader):
return super(Synology, self).isEnabled(manual, data) and\
((self.conf('use_for') in for_protocol))
class SynologyRPC(object):
"""SynologyRPC lite library"""
@@ -118,11 +106,11 @@ class SynologyRPC(object):
if response['success']:
log.info('Synology action successfull')
return response
except requests.ConnectionError as err:
except requests.ConnectionError, err:
log.error('Synology connection error, check your config %s', err)
except requests.HTTPError as err:
except requests.HTTPError, err:
log.error('SynologyRPC HTTPError: %s', err)
except Exception as err:
except Exception, err:
log.error('Exception: %s', err)
finally:
return response
@@ -157,6 +145,3 @@ class SynologyRPC(object):
self._logout()
return result
def test(self):
return bool(self._login())

View File

@@ -1,6 +1,5 @@
from .main import Transmission
def start():
return Transmission()

View File

@@ -1,7 +1,7 @@
from base64 import b64encode
from couchpotato.core.downloaders.base import Downloader, ReleaseDownloadList
from couchpotato.core.helpers.encoding import isInt, sp
from couchpotato.core.helpers.variable import tryInt, tryFloat, cleanHost
from couchpotato.core.helpers.variable import tryInt, tryFloat
from couchpotato.core.logger import CPLog
from datetime import timedelta
import httplib
@@ -19,15 +19,15 @@ class Transmission(Downloader):
log = CPLog(__name__)
trpc = None
def connect(self, reconnect = False):
def connect(self):
# Load host from config and split out port.
host = cleanHost(self.conf('host'), protocol = False).split(':')
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
if not self.trpc or reconnect:
self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url').strip('/ '), username = self.conf('username'), password = self.conf('password'))
if not self.trpc:
self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url'), username = self.conf('username'), password = self.conf('password'))
return self.trpc
@@ -83,11 +83,6 @@ class Transmission(Downloader):
log.info('Torrent sent to Transmission successfully.')
return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
def test(self):
if self.connect(True) and self.trpc.get_session():
return True
return False
def getAllDownloadStatus(self, ids):
log.debug('Checking Transmission download status.')
@@ -101,7 +96,6 @@ class Transmission(Downloader):
'fields': ['id', 'name', 'hashString', 'percentDone', 'status', 'eta', 'isStalled', 'isFinished', 'downloadDir', 'uploadRatio', 'secondsSeeding', 'seedIdleLimit', 'files']
}
session = self.trpc.get_session()
queue = self.trpc.get_alltorrents(return_params)
if not (queue and queue.get('torrents')):
log.debug('Nothing in queue or error')
@@ -109,9 +103,13 @@ class Transmission(Downloader):
for torrent in queue['torrents']:
if torrent['hashString'] in ids:
log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / isStalled=%s / eta=%s / uploadRatio=%s / isFinished=%s / incomplete-dir-enabled=%s / incomplete-dir=%s',
(torrent['name'], torrent['id'], torrent['downloadDir'], torrent['hashString'], torrent['percentDone'], torrent['status'], torrent.get('isStalled', 'N/A'), torrent['eta'], torrent['uploadRatio'], torrent['isFinished'], session['incomplete-dir-enabled'], session['incomplete-dir']))
log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / isStalled=%s / eta=%s / uploadRatio=%s / isFinished=%s',
(torrent['name'], torrent['id'], torrent['downloadDir'], torrent['hashString'], torrent['percentDone'], torrent['status'], torrent.get('isStalled', 'N/A'), torrent['eta'], torrent['uploadRatio'], torrent['isFinished']))
torrent_files = []
for file_item in torrent['files']:
torrent_files.append(sp(os.path.join(torrent['downloadDir'], file_item['name'])))
status = 'busy'
if torrent.get('isStalled') and not torrent['percentDone'] == 1 and self.conf('stalled_as_failed'):
status = 'failed'
@@ -119,16 +117,7 @@ class Transmission(Downloader):
status = 'completed'
elif torrent['status'] in [5, 6]:
status = 'seeding'
if session['incomplete-dir-enabled'] and status == 'busy':
torrent_folder = session['incomplete-dir']
else:
torrent_folder = torrent['downloadDir']
torrent_files = []
for file_item in torrent['files']:
torrent_files.append(sp(os.path.join(torrent_folder, file_item['name'])))
release_downloads.append({
'id': torrent['hashString'],
'name': torrent['name'],
@@ -136,7 +125,7 @@ class Transmission(Downloader):
'original_status': torrent['status'],
'seed_ratio': torrent['uploadRatio'],
'timeleft': str(timedelta(seconds = torrent['eta'])),
'folder': sp(torrent_folder if len(torrent_files) == 1 else os.path.join(torrent_folder, torrent['name'])),
'folder': sp(torrent['downloadDir'] if len(torrent_files) == 1 else os.path.join(torrent['downloadDir'], torrent['name'])),
'files': '|'.join(torrent_files)
})
@@ -192,10 +181,10 @@ class TransmissionRPC(object):
else:
log.debug('Unknown failure sending command to Transmission. Return text is: %s', response['result'])
return False
except httplib.InvalidURL as err:
except httplib.InvalidURL, err:
log.error('Invalid Transmission host, check your config %s', err)
return False
except urllib2.HTTPError as err:
except urllib2.HTTPError, err:
if err.code == 401:
log.error('Invalid Transmission Username or Password, check your config')
return False
@@ -213,7 +202,7 @@ class TransmissionRPC(object):
log.error('Unable to get Transmission Session-Id %s', err)
else:
log.error('TransmissionRPC HTTPError: %s', err)
except urllib2.URLError as err:
except urllib2.URLError, err:
log.error('Unable to connect to Transmission %s', err)
def get_session(self):

View File

@@ -1,6 +1,5 @@
from .main import uTorrent
def start():
return uTorrent()
@@ -24,7 +23,7 @@ config = [{
{
'name': 'host',
'default': 'localhost:8000',
'description': 'Port can be found in settings when enabling WebUI.',
'description': 'Hostname with port. Usually <strong>localhost:8000</strong>',
},
{
'name': 'username',

View File

@@ -2,7 +2,7 @@ from base64 import b16encode, b32decode
from bencode import bencode as benc, bdecode
from couchpotato.core.downloaders.base import Downloader, ReleaseDownloadList
from couchpotato.core.helpers.encoding import isInt, ss, sp
from couchpotato.core.helpers.variable import tryInt, tryFloat, cleanHost
from couchpotato.core.helpers.variable import tryInt, tryFloat
from couchpotato.core.logger import CPLog
from datetime import timedelta
from hashlib import sha1
@@ -37,7 +37,7 @@ class uTorrent(Downloader):
def connect(self):
# Load host from config and split out port.
host = cleanHost(self.conf('host'), protocol = False).split(':')
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
@@ -66,7 +66,7 @@ class uTorrent(Downloader):
new_settings['seed_prio_limitul_flag'] = True
log.info('Updated uTorrent settings to set a torrent to complete after it the seeding requirements are met.')
if settings.get('bt.read_only_on_complete'): #This doesn't work as this option seems to be not available through the api. Mitigated with removeReadOnly function
if settings.get('bt.read_only_on_complete'): #This doesn't work as this option seems to be not available through the api. Mitigated with removeReadOnly function
new_settings['bt.read_only_on_complete'] = False
log.info('Updated uTorrent settings to not set the files to read only after completing.')
@@ -115,17 +115,6 @@ class uTorrent(Downloader):
return self.downloadReturnId(torrent_hash)
def test(self):
if self.connect():
build_version = self.utorrent_api.get_build()
if not build_version:
return False
if build_version < 25406: # This build corresponds to version 3.0.0 stable
return False, 'Your uTorrent client is too old, please update to newest version.'
return True
return False
def getAllDownloadStatus(self, ids):
log.debug('Checking uTorrent download status.')
@@ -160,7 +149,7 @@ class uTorrent(Downloader):
torrent_files = [sp(os.path.join(torrent[26], torrent_file[0])) for torrent_file in torrent_files['files'][1]]
except:
log.debug('Failed getting files from torrent: %s', torrent[2])
status = 'busy'
if (torrent[1] & self.status_flags['STARTED'] or torrent[1] & self.status_flags['QUEUED']) and torrent[4] == 1000:
status = 'seeding'
@@ -168,10 +157,10 @@ class uTorrent(Downloader):
status = 'failed'
elif torrent[4] == 1000:
status = 'completed'
if not status == 'busy':
self.removeReadOnly(torrent_files)
release_downloads.append({
'id': torrent[0],
'name': torrent[2],
@@ -242,14 +231,14 @@ class uTorrentAPI(object):
return response
else:
log.debug('Unknown failure sending command to uTorrent. Return text is: %s', response)
except httplib.InvalidURL as err:
except httplib.InvalidURL, err:
log.error('Invalid uTorrent host, check your config %s', err)
except urllib2.HTTPError as err:
except urllib2.HTTPError, err:
if err.code == 401:
log.error('Invalid uTorrent Username or Password, check your config')
else:
log.error('uTorrent HTTPError: %s', err)
except urllib2.URLError as err:
except urllib2.URLError, err:
log.error('Unable to connect to uTorrent %s', err)
return False
@@ -272,7 +261,7 @@ class uTorrentAPI(object):
def set_torrent(self, hash, params):
action = 'action=setprops&hash=%s' % hash
for k, v in params.items():
for k, v in params.iteritems():
action += '&s=%s&v=%s' % (k, v)
return self._request(action)
@@ -315,7 +304,7 @@ class uTorrentAPI(object):
#log.debug('uTorrent settings: %s', settings_dict)
except Exception as err:
except Exception, err:
log.error('Failed to get settings from uTorrent: %s', err)
return settings_dict
@@ -333,10 +322,3 @@ class uTorrentAPI(object):
def get_files(self, hash):
action = 'action=getfiles&hash=%s' % hash
return self._request(action)
def get_build(self):
data = self._request('')
if not data:
return False
response = json.loads(data)
return int(response.get('build'))

View File

@@ -1,5 +1,5 @@
from axl.axel import Event
from couchpotato.core.helpers.variable import mergeDicts, natsortKey
from couchpotato.core.helpers.variable import mergeDicts, natcmp
from couchpotato.core.logger import CPLog
import threading
import traceback
@@ -7,7 +7,6 @@ import traceback
log = CPLog(__name__)
events = {}
def runHandler(name, handler, *args, **kwargs):
try:
return handler(*args, **kwargs)
@@ -15,7 +14,6 @@ def runHandler(name, handler, *args, **kwargs):
from couchpotato.environment import Env
log.error('Error in event "%s", that wasn\'t caught: %s%s', (name, traceback.format_exc(), Env.all() if not Env.get('dev') else ''))
def addEvent(name, handler, priority = 100):
if not events.get(name):
@@ -29,7 +27,7 @@ def addEvent(name, handler, priority = 100):
has_parent = hasattr(handler, 'im_self')
parent = None
if has_parent:
parent = handler.__self__
parent = handler.im_self
bc = hasattr(parent, 'beforeCall')
if bc: parent.beforeCall(handler)
@@ -50,19 +48,22 @@ def addEvent(name, handler, priority = 100):
'priority': priority,
})
def removeEvent(name, handler):
e = events[name]
e -= handler
def fireEvent(name, *args, **kwargs):
if name not in events: return
if not events.has_key(name): return
#log.debug('Firing event %s', name)
try:
options = {
'is_after_event': False, # Fire after event
'on_complete': False, # onComplete event
'single': False, # Return single handler
'merge': False, # Merge items
'in_order': False, # Fire them in specific order, waits for the other to finish
'is_after_event': False, # Fire after event
'on_complete': False, # onComplete event
'single': False, # Return single handler
'merge': False, # Merge items
'in_order': False, # Fire them in specific order, waits for the other to finish
}
# Do options
@@ -100,14 +101,11 @@ def fireEvent(name, *args, **kwargs):
# Fire
result = e(*args, **kwargs)
result_keys = result.keys()
result_keys.sort(key = natsortKey)
if options['single'] and not options['merge']:
results = None
# Loop over results, stop when first not None result is found.
for r_key in result_keys:
for r_key in sorted(result.iterkeys(), cmp = natcmp):
r = result[r_key]
if r[0] is True and r[1] is not None:
results = r[1]
@@ -119,7 +117,7 @@ def fireEvent(name, *args, **kwargs):
else:
results = []
for r_key in result_keys:
for r_key in sorted(result.iterkeys(), cmp = natcmp):
r = result[r_key]
if r[0] == True and r[1]:
results.append(r[1])
@@ -162,21 +160,18 @@ def fireEvent(name, *args, **kwargs):
except Exception:
log.error('%s: %s', (name, traceback.format_exc()))
def fireEventAsync(*args, **kwargs):
try:
t = threading.Thread(target = fireEvent, args = args, kwargs = kwargs)
t.setDaemon(True)
t.start()
return True
except Exception as e:
except Exception, e:
log.error('%s: %s', (args[0], e))
def errorHandler(error):
etype, value, tb = error
log.error(''.join(traceback.format_exception(etype, value, tb)))
def getEvent(name):
return events[name]

View File

@@ -5,32 +5,29 @@ import os
import re
import traceback
import unicodedata
import six
log = CPLog(__name__)
def toSafeString(original):
valid_chars = "-_.() %s%s" % (ascii_letters, digits)
cleaned_filename = unicodedata.normalize('NFKD', toUnicode(original)).encode('ASCII', 'ignore')
valid_string = ''.join(c for c in cleaned_filename if c in valid_chars)
cleanedFilename = unicodedata.normalize('NFKD', toUnicode(original)).encode('ASCII', 'ignore')
valid_string = ''.join(c for c in cleanedFilename if c in valid_chars)
return ' '.join(valid_string.split())
def simplifyString(original):
string = stripAccents(original.lower())
string = toSafeString(' '.join(re.split('\W+', string)))
split = re.split('\W+|_', string.lower())
return toUnicode(' '.join(split))
def toUnicode(original, *args):
try:
if isinstance(original, unicode):
return original
else:
try:
return six.text_type(original, *args)
return unicode(original, *args)
except:
try:
return ek(original, *args)
@@ -41,18 +38,16 @@ def toUnicode(original, *args):
ascii_text = str(original).encode('string_escape')
return toUnicode(ascii_text)
def ss(original, *args):
u_original = toUnicode(original, *args)
try:
from couchpotato.environment import Env
return u_original.encode(Env.get('encoding'))
except Exception as e:
except Exception, e:
log.debug('Failed ss encoding char, force UTF8: %s', e)
return u_original.encode('UTF-8')
def sp(path, *args):
# Standardise encoding, normalise case, path and strip trailing '/' or '\'
@@ -63,7 +58,7 @@ def sp(path, *args):
if os.path.sep == '/' and '\\' in path:
path = '/' + path.replace(':', '').replace('\\', '/')
path = os.path.normpath(ss(path, *args))
path = os.path.normcase(os.path.normpath(ss(path, *args)))
# Remove any trailing path separators
if path != os.path.sep:
@@ -78,7 +73,6 @@ def sp(path, *args):
return path
def ek(original, *args):
if isinstance(original, (str, unicode)):
try:
@@ -89,7 +83,6 @@ def ek(original, *args):
return original
def isInt(value):
try:
int(value)
@@ -97,16 +90,14 @@ def isInt(value):
except ValueError:
return False
def stripAccents(s):
return ''.join((c for c in unicodedata.normalize('NFD', toUnicode(s)) if unicodedata.category(c) != 'Mn'))
def tryUrlencode(s):
new = six.u('')
new = u''
if isinstance(s, dict):
for key, value in s.items():
new += six.u('&%s=%s') % (key, tryUrlencode(value))
for key, value in s.iteritems():
new += u'&%s=%s' % (key, tryUrlencode(value))
return new[1:]
else:

View File

@@ -1,5 +1,5 @@
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import natsortKey
from couchpotato.core.helpers.variable import natcmp
from urllib import unquote
import re
@@ -8,13 +8,8 @@ def getParams(params):
reg = re.compile('^[a-z0-9_\.]+$')
# Sort keys
param_keys = params.keys()
param_keys.sort(key = natsortKey)
temp = {}
for param in param_keys:
value = params[param]
for param, value in sorted(params.iteritems()):
nest = re.split("([\[\]]+)", param)
if len(nest) > 1:
@@ -42,17 +37,13 @@ def getParams(params):
return dictToList(temp)
def dictToList(params):
if type(params) is dict:
new = {}
for x, value in params.items():
for x, value in params.iteritems():
try:
convert = lambda text: int(text) if text.isdigit() else text.lower()
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
sorted_keys = sorted(value.keys(), key = alphanum_key)
new_value = [dictToList(value[k]) for k in sorted_keys]
new_value = [dictToList(value[k]) for k in sorted(value.iterkeys(), cmp = natcmp)]
except:
new_value = value

View File

@@ -3,7 +3,6 @@ import xml.etree.ElementTree as XMLTree
log = CPLog(__name__)
class RSS(object):
def getTextElements(self, xml, path):
@@ -47,6 +46,6 @@ class RSS(object):
def getItems(self, data, path = 'channel/item'):
try:
return XMLTree.parse(data).findall(path)
except Exception as e:
except Exception, e:
log.error('Error parsing RSS. %s', e)
return []

View File

@@ -8,32 +8,26 @@ import random
import re
import string
import sys
import six
from six.moves import map, zip, filter
log = CPLog(__name__)
def fnEscape(pattern):
return pattern.replace('[', '[[').replace(']', '[]]').replace('[[', '[[]')
return pattern.replace('[','[[').replace(']','[]]').replace('[[','[[]')
def link(src, dst):
if os.name == 'nt':
import ctypes
if ctypes.windll.kernel32.CreateHardLinkW(six.text_type(dst), six.text_type(src), 0) == 0: raise ctypes.WinError()
if ctypes.windll.kernel32.CreateHardLinkW(unicode(dst), unicode(src), 0) == 0: raise ctypes.WinError()
else:
os.link(src, dst)
def symlink(src, dst):
if os.name == 'nt':
import ctypes
if ctypes.windll.kernel32.CreateSymbolicLinkW(six.text_type(dst), six.text_type(src), 1 if os.path.isdir(src) else 0) in [0, 1280]: raise ctypes.WinError()
if ctypes.windll.kernel32.CreateSymbolicLinkW(unicode(dst), unicode(src), 1 if os.path.isdir(src) else 0) in [0, 1280]: raise ctypes.WinError()
else:
os.symlink(src, dst)
def getUserDir():
try:
import pwd
@@ -43,7 +37,6 @@ def getUserDir():
return os.path.expanduser('~')
def getDownloadDir():
user_dir = getUserDir()
@@ -56,7 +49,6 @@ def getDownloadDir():
return user_dir
def getDataDir():
# Windows
@@ -76,10 +68,8 @@ def getDataDir():
# Linux
return os.path.join(user_dir, '.couchpotato')
def isDict(obj):
return isinstance(obj, dict)
def isDict(object):
return isinstance(object, dict)
def mergeDicts(a, b, prepend_list = False):
assert isDict(a), isDict(b)
@@ -101,7 +91,6 @@ def mergeDicts(a, b, prepend_list = False):
current_dst[key] = current_src[key]
return dst
def removeListDuplicates(seq):
checked = []
for e in seq:
@@ -109,73 +98,35 @@ def removeListDuplicates(seq):
checked.append(e)
return checked
def flattenList(l):
if isinstance(l, list):
return sum(map(flattenList, l))
else:
return l
def md5(text):
return hashlib.md5(ss(text)).hexdigest()
def sha1(text):
return hashlib.sha1(text).hexdigest()
def isLocalIP(ip):
ip = ip.lstrip('htps:/')
regex = '/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1)$/'
return re.search(regex, ip) is not None or 'localhost' in ip or ip[:4] == '127.'
def getExt(filename):
return os.path.splitext(filename)[1][1:]
def cleanHost(host):
if not host.startswith(('http://', 'https://')):
host = 'http://' + host
def cleanHost(host, protocol = True, ssl = False, username = None, password = None):
"""Return a cleaned up host with given url options set
Changes protocol to https if ssl is set to True and http if ssl is set to false.
>>> cleanHost("localhost:80", ssl=True)
'https://localhost:80/'
>>> cleanHost("localhost:80", ssl=False)
'http://localhost:80/'
Username and password is managed with the username and password variables
>>> cleanHost("localhost:80", username="user", password="passwd")
'http://user:passwd@localhost:80/'
Output without scheme (protocol) can be forced with protocol=False
>>> cleanHost("localhost:80", protocol=False)
'localhost:80'
"""
if not '://' in host and protocol:
host = ('https://' if ssl else 'http://') + host
if not protocol:
host = host.split('://', 1)[-1]
if protocol and username and password:
try:
auth = re.findall('^(?:.+?//)(.+?):(.+?)@(?:.+)$', host)
if auth:
log.error('Cleanhost error: auth already defined in url: %s, please remove BasicAuth from url.', host)
else:
host = host.replace('://', '://%s:%s@' % (username, password), 1)
except:
pass
host = host.rstrip('/ ')
if protocol:
host += '/'
host = host.rstrip('/')
host += '/'
return host
def getImdb(txt, check_inside = False, multiple = False):
if not check_inside:
@@ -192,7 +143,7 @@ def getImdb(txt, check_inside = False, multiple = False):
ids = re.findall('(tt\d{4,7})', txt)
if multiple:
return removeDuplicate(['tt%07d' % tryInt(x[2:]) for x in ids]) if len(ids) > 0 else []
return list(set(['tt%07d' % tryInt(x[2:]) for x in ids])) if len(ids) > 0 else []
return 'tt%07d' % tryInt(ids[0][2:])
except IndexError:
@@ -200,12 +151,10 @@ def getImdb(txt, check_inside = False, multiple = False):
return False
def tryInt(s, default = 0):
try: return int(s)
except: return default
def tryFloat(s):
try:
if isinstance(s, str):
@@ -214,17 +163,17 @@ def tryFloat(s):
return float(s)
except: return 0
def natsortKey(string_):
"""See http://www.codinghorror.com/blog/archives/001018.html"""
return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)]
def natsortKey(s):
return map(tryInt, re.findall(r'(\d+|\D+)', s))
def natcmp(a, b):
return cmp(natsortKey(a), natsortKey(b))
def toIterable(value):
if isinstance(value, collections.Iterable):
if type(value) in [list, tuple]:
return value
return [value]
def getTitle(library_dict):
try:
try:
@@ -247,7 +196,6 @@ def getTitle(library_dict):
log.error('Could not get title for library item: %s', library_dict)
return None
def possibleTitles(raw_title):
titles = [
@@ -260,42 +208,18 @@ def possibleTitles(raw_title):
new_title = raw_title.replace('&', 'and')
titles.append(simplifyString(new_title))
return removeDuplicate(titles)
return list(set(titles))
def randomString(size = 8, chars = string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
def splitString(str, split_on = ',', clean = True):
l = [x.strip() for x in str.split(split_on)] if str else []
return removeEmpty(l) if clean else l
def removeEmpty(l):
return list(filter(None, l))
def removeDuplicate(l):
seen = set()
return [x for x in l if x not in seen and not seen.add(x)]
list = [x.strip() for x in str.split(split_on)] if str else []
return filter(None, list) if clean else list
def dictIsSubset(a, b):
return all([k in b and b[k] == v for k, v in a.items()])
def isSubFolder(sub_folder, base_folder):
# Returns True if sub_folder is the same as or inside base_folder
return base_folder and sub_folder and ss(os.path.normpath(base_folder).rstrip(os.path.sep) + os.path.sep) in ss(os.path.normpath(sub_folder).rstrip(os.path.sep) + os.path.sep)
# From SABNZBD
re_password = [re.compile(r'([^/\\]+)[/\\](.+)'), re.compile(r'(.+){{([^{}]+)}}$'), re.compile(r'(.+)\s+password\s*=\s*(.+)$', re.I)]
def scanForPassword(name):
m = None
for reg in re_password:
m = reg.search(name)
if m: break
if m:
return m.group(1).strip('. '), m.group(2).strip()
# Returns True is sub_folder is the same as or in base_folder
return base_folder.rstrip(os.path.sep) + os.path.sep in sub_folder.rstrip(os.path.sep) + os.path.sep

View File

@@ -1,10 +1,9 @@
from couchpotato.core.event import fireEvent
from couchpotato.core.logger import CPLog
from importhelper import import_module
from importlib import import_module
import os
import sys
import traceback
import six
log = CPLog(__name__)
@@ -38,7 +37,7 @@ class Loader(object):
self.paths['custom_plugins'] = (30, '', custom_plugin_dir)
# Loop over all paths and add to module list
for plugin_type, plugin_tuple in self.paths.items():
for plugin_type, plugin_tuple in self.paths.iteritems():
priority, module, dir_name = plugin_tuple
self.addFromDir(plugin_type, priority, module, dir_name)
@@ -46,7 +45,7 @@ class Loader(object):
did_save = 0
for priority in sorted(self.modules):
for module_name, plugin in sorted(self.modules[priority].items()):
for module_name, plugin in sorted(self.modules[priority].iteritems()):
# Load module
try:
@@ -82,7 +81,7 @@ class Loader(object):
for filename in os.listdir(root_path):
path = os.path.join(root_path, filename)
if os.path.isdir(path) and filename[:2] != '__':
if six.u('__init__.py') in os.listdir(path):
if u'__init__.py' in os.listdir(path):
new_base_path = ''.join(s + '.' for s in base_path) + filename
self.paths[new_base_path.replace('.', '_')] = (priority, new_base_path, path)

View File

@@ -1,7 +1,6 @@
import logging
import re
class CPLog(object):
context = ''
@@ -38,7 +37,7 @@ class CPLog(object):
def safeMessage(self, msg, replace_tuple = ()):
from couchpotato.environment import Env
from couchpotato.core.helpers.encoding import ss, toUnicode
from couchpotato.core.helpers.encoding import ss
msg = ss(msg)
@@ -50,8 +49,8 @@ class CPLog(object):
msg = msg % tuple([ss(x) for x in list(replace_tuple)])
else:
msg = msg % ss(replace_tuple)
except Exception as e:
self.logger.error('Failed encoding stuff to log "%s": %s' % (msg, e))
except Exception, e:
self.logger.error(u'Failed encoding stuff to log "%s": %s' % (msg, e))
if not Env.get('dev'):
@@ -67,4 +66,4 @@ class CPLog(object):
except:
pass
return toUnicode(msg)
return msg

View File

@@ -1,11 +1,9 @@
import traceback
from couchpotato import get_session, CPLog
from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
from couchpotato.core.helpers.variable import mergeDicts
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Media
log = CPLog(__name__)
class MediaBase(Plugin):
@@ -13,13 +11,20 @@ class MediaBase(Plugin):
default_dict = {
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}, 'files': {}, 'info': {}},
'library': {'titles': {}, 'files': {}},
'releases': {'status': {}, 'quality': {}, 'files':{}, 'info': {}},
'library': {'titles': {}, 'files':{}},
'files': {},
'status': {},
'category': {},
}
search_dict = mergeDicts({
'library': {
'related_libraries': {},
'root_library': {}
},
}, default_dict)
def initType(self):
addEvent('media.types', self.getType)
@@ -29,33 +34,19 @@ class MediaBase(Plugin):
def createOnComplete(self, id):
def onComplete():
try:
db = get_session()
media = db.query(Media).filter_by(id = id).first()
media_dict = media.to_dict(self.default_dict)
event_name = '%s.searcher.single' % media.type
fireEvent(event_name, media_dict, on_complete = self.createNotifyFront(id))
except:
log.error('Failed creating onComplete: %s', traceback.format_exc())
finally:
db.close()
db = get_session()
media = db.query(Media).filter_by(id = id).first()
fireEventAsync('%s.searcher.single' % media.type, media.to_dict(self.search_dict), on_complete = self.createNotifyFront(id))
db.expire_all()
return onComplete
def createNotifyFront(self, media_id):
def notifyFront():
try:
db = get_session()
media = db.query(Media).filter_by(id = media_id).first()
media_dict = media.to_dict(self.default_dict)
event_name = '%s.update' % media.type
fireEvent('notify.frontend', type = event_name, data = media_dict)
except:
log.error('Failed creating onComplete: %s', traceback.format_exc())
finally:
db.close()
db = get_session()
media = db.query(Media).filter_by(id = media_id).first()
fireEvent('notify.frontend', type = '%s.update' % media.type, data = media.to_dict(self.default_dict))
db.expire_all()
return notifyFront

View File

@@ -1,13 +1,6 @@
from couchpotato.core.event import addEvent
from couchpotato.core.plugins.base import Plugin
from .main import Library
def start():
return Library()
class LibraryBase(Plugin):
_type = None
def initType(self):
addEvent('library.types', self.getType)
def getType(self):
return self._type
config = []

View File

@@ -0,0 +1,13 @@
from couchpotato.core.event import addEvent
from couchpotato.core.plugins.base import Plugin
class LibraryBase(Plugin):
_type = None
def initType(self):
addEvent('library.types', self.getType)
def getType(self):
return self._type

View File

@@ -0,0 +1,18 @@
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.media._base.library.base import LibraryBase
class Library(LibraryBase):
def __init__(self):
addEvent('library.title', self.title)
def title(self, library):
return fireEvent(
'library.query',
library,
condense = False,
include_year = False,
include_identifier = False,
single = True
)

View File

@@ -0,0 +1,6 @@
from .main import Matcher
def start():
return Matcher()
config = []

View File

@@ -0,0 +1,84 @@
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import simplifyString
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
log = CPLog(__name__)
class MatcherBase(Plugin):
type = None
def __init__(self):
if self.type:
addEvent('%s.matcher.correct' % self.type, self.correct)
def correct(self, chain, release, media, quality):
raise NotImplementedError()
def flattenInfo(self, info):
# Flatten dictionary of matches (chain info)
if isinstance(info, dict):
return dict([(key, self.flattenInfo(value)) for key, value in info.items()])
# Flatten matches
result = None
for match in info:
if isinstance(match, dict):
if result is None:
result = {}
for key, value in match.items():
if key not in result:
result[key] = []
result[key].append(value)
else:
if result is None:
result = []
result.append(match)
return result
def constructFromRaw(self, match):
if not match:
return None
parts = [
''.join([
y for y in x[1:] if y
]) for x in match
]
return ''.join(parts)[:-1].strip()
def simplifyValue(self, value):
if not value:
return value
if isinstance(value, basestring):
return simplifyString(value)
if isinstance(value, list):
return [self.simplifyValue(x) for x in value]
raise ValueError("Unsupported value type")
def chainMatch(self, chain, group, tags):
info = self.flattenInfo(chain.info[group])
found_tags = []
for tag, accepted in tags.items():
values = [self.simplifyValue(x) for x in info.get(tag, [None])]
if any([val in accepted for val in values]):
found_tags.append(tag)
log.debug('tags found: %s, required: %s' % (found_tags, tags.keys()))
if set(tags.keys()) == set(found_tags):
return True
return all([key in found_tags for key, value in tags.items()])

View File

@@ -0,0 +1,88 @@
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.variable import possibleTitles
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.matcher.base import MatcherBase
from caper import Caper
log = CPLog(__name__)
class Matcher(MatcherBase):
def __init__(self):
super(Matcher, self).__init__()
self.caper = Caper()
addEvent('matcher.parse', self.parse)
addEvent('matcher.match', self.match)
addEvent('matcher.flatten_info', self.flattenInfo)
addEvent('matcher.construct_from_raw', self.constructFromRaw)
addEvent('matcher.correct_title', self.correctTitle)
addEvent('matcher.correct_quality', self.correctQuality)
def parse(self, name, parser='scene'):
return self.caper.parse(name, parser)
def match(self, release, media, quality):
match = fireEvent('matcher.parse', release['name'], single = True)
if len(match.chains) < 1:
log.info2('Wrong: %s, unable to parse release name (no chains)', release['name'])
return False
for chain in match.chains:
if fireEvent('%s.matcher.correct' % media['type'], chain, release, media, quality, single = True):
return chain
return False
def correctTitle(self, chain, media):
root_library = media['library']['root_library']
if 'show_name' not in chain.info or not len(chain.info['show_name']):
log.info('Wrong: missing show name in parsed result')
return False
# Get the lower-case parsed show name from the chain
chain_words = [x.lower() for x in chain.info['show_name']]
# Build a list of possible titles of the media we are searching for
titles = root_library['info']['titles']
# Add year suffix titles (will result in ['<name_one>', '<name_one> <suffix_one>', '<name_two>', ...])
suffixes = [None, root_library['info']['year']]
titles = [
title + ((' %s' % suffix) if suffix else '')
for title in titles
for suffix in suffixes
]
# Check show titles match
# TODO check xem names
for title in titles:
for valid_words in [x.split(' ') for x in possibleTitles(title)]:
if valid_words == chain_words:
return True
return False
def correctQuality(self, chain, quality, quality_map):
if quality['identifier'] not in quality_map:
log.info2('Wrong: unknown preferred quality %s', quality['identifier'])
return False
if 'video' not in chain.info:
log.info2('Wrong: no video tags found')
return False
video_tags = quality_map[quality['identifier']]
if not self.chainMatch(chain, 'video', video_tags):
log.info2('Wrong: %s tags not in chain', video_tags)
return False
return True

View File

@@ -1,6 +1,5 @@
from .main import MediaPlugin
def start():
return MediaPlugin()

View File

@@ -1,9 +1,8 @@
import traceback
from couchpotato import get_session, tryInt
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import mergeDicts, splitString, getImdb, getTitle
from couchpotato.core.helpers.variable import mergeDicts, splitString, getImdb
from couchpotato.core.logger import CPLog
from couchpotato.core.media import MediaBase
from couchpotato.core.settings.model import Library, LibraryTitle, Release, \
@@ -71,39 +70,26 @@ class MediaPlugin(MediaBase):
addEvent('media.restatus', self.restatus)
def refresh(self, id = '', **kwargs):
handlers = []
ids = splitString(id)
db = get_session()
for x in ids:
for x in splitString(id):
media = db.query(Media).filter_by(id = x).first()
refresh_handler = self.createRefreshHandler(x)
if refresh_handler:
handlers.append(refresh_handler)
if media:
# Get current selected title
default_title = ''
for title in media.library.titles:
if title.default: default_title = title.title
fireEvent('notify.frontend', type = 'media.busy', data = {'id': [tryInt(x) for x in ids]})
fireEventAsync('schedule.queue', handlers = handlers)
fireEvent('notify.frontend', type = '%s.busy' % media.type, data = {'id': x})
fireEventAsync('library.update.%s' % media.type, identifier = media.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(x))
db.expire_all()
return {
'success': True,
}
def createRefreshHandler(self, id):
db = get_session()
media = db.query(Media).filter_by(id = id).first()
if media:
default_title = getTitle(media.library)
identifier = media.library.identifier
event = 'library.update.%s' % media.type
def handler():
fireEvent(event, identifier = identifier, default_title = default_title, on_complete = self.createOnComplete(id))
if handler:
return handler
def addSingleRefreshView(self):
for media_type in fireEvent('media.types', merge = True):
@@ -124,6 +110,7 @@ class MediaPlugin(MediaBase):
if m:
results = m.to_dict(self.default_dict)
db.expire_all()
return results
def getView(self, id = None, **kwargs):
@@ -216,14 +203,14 @@ class MediaPlugin(MediaBase):
# List release statuses
releases = db.query(Release) \
.filter(Release.movie_id.in_(media_ids)) \
.filter(Release.media_id.in_(media_ids)) \
.all()
release_statuses = dict((m, set()) for m in media_ids)
releases_count = dict((m, 0) for m in media_ids)
for release in releases:
release_statuses[release.movie_id].add('%d,%d' % (release.status_id, release.quality_id))
releases_count[release.movie_id] += 1
release_statuses[release.media_id].add('%d,%d' % (release.status_id, release.quality_id))
releases_count[release.media_id] += 1
# Get main movie data
q2 = db.query(Media) \
@@ -252,13 +239,14 @@ class MediaPlugin(MediaBase):
# Merge releases with movie dict
movies.append(mergeDicts(movie_dict[media_id].to_dict({
'library': {'titles': {}, 'files': {}},
'library': {'titles': {}, 'files':{}},
'files': {},
}), {
'releases': releases,
'releases_count': releases_count.get(media_id),
}))
db.expire_all()
return total_count, movies
def listView(self, **kwargs):
@@ -352,6 +340,7 @@ class MediaPlugin(MediaBase):
if len(chars) == 25:
break
db.expire_all()
return ''.join(sorted(chars))
def charView(self, **kwargs):
@@ -376,55 +365,50 @@ class MediaPlugin(MediaBase):
def delete(self, media_id, delete_from = None):
try:
db = get_session()
db = get_session()
media = db.query(Media).filter_by(id = media_id).first()
if media:
deleted = False
if delete_from == 'all':
media = db.query(Media).filter_by(id = media_id).first()
if media:
deleted = False
if delete_from == 'all':
db.delete(media)
db.commit()
deleted = True
else:
done_status = fireEvent('status.get', 'done', single = True)
total_releases = len(media.releases)
total_deleted = 0
new_movie_status = None
for release in media.releases:
if delete_from in ['wanted', 'snatched', 'late']:
if release.status_id != done_status.get('id'):
db.delete(release)
total_deleted += 1
new_movie_status = 'done'
elif delete_from == 'manage':
if release.status_id == done_status.get('id'):
db.delete(release)
total_deleted += 1
new_movie_status = 'active'
db.commit()
if total_releases == total_deleted:
db.delete(media)
db.commit()
deleted = True
else:
done_status = fireEvent('status.get', 'done', single = True)
total_releases = len(media.releases)
total_deleted = 0
new_movie_status = None
for release in media.releases:
if delete_from in ['wanted', 'snatched', 'late']:
if release.status_id != done_status.get('id'):
db.delete(release)
total_deleted += 1
new_movie_status = 'done'
elif delete_from == 'manage':
if release.status_id == done_status.get('id'):
db.delete(release)
total_deleted += 1
new_movie_status = 'active'
elif new_movie_status:
new_status = fireEvent('status.get', new_movie_status, single = True)
media.profile_id = None
media.status_id = new_status.get('id')
db.commit()
else:
fireEvent('media.restatus', media.id, single = True)
if total_releases == total_deleted:
db.delete(media)
db.commit()
deleted = True
elif new_movie_status:
new_status = fireEvent('status.get', new_movie_status, single = True)
media.profile_id = None
media.status_id = new_status.get('id')
db.commit()
else:
fireEvent('media.restatus', media.id, single = True)
if deleted:
fireEvent('notify.frontend', type = 'movie.deleted', data = media.to_dict())
except:
log.error('Failed deleting media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
if deleted:
fireEvent('notify.frontend', type = 'movie.deleted', data = media.to_dict())
db.expire_all()
return True
def deleteView(self, id = '', **kwargs):
@@ -448,33 +432,27 @@ class MediaPlugin(MediaBase):
active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
try:
db = get_session()
db = get_session()
m = db.query(Media).filter_by(id = media_id).first()
if not m or len(m.library.titles) == 0:
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
return False
m = db.query(Media).filter_by(id = media_id).first()
if not m or len(m.library.titles) == 0:
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
return False
log.debug('Changing status for %s', m.library.titles[0].title)
if not m.profile:
m.status_id = done_status.get('id')
else:
move_to_wanted = True
log.debug('Changing status for %s', m.library.titles[0].title)
if not m.profile:
m.status_id = done_status.get('id')
else:
move_to_wanted = True
for t in m.profile.types:
for release in m.releases:
if t.quality.identifier is release.quality.identifier and (release.status_id is done_status.get('id') and t.finish):
move_to_wanted = False
for t in m.profile.types:
for release in m.releases:
if t.quality.identifier is release.quality.identifier and (release.status_id is done_status.get('id') and t.finish):
move_to_wanted = False
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
db.commit()
db.commit()
return True
except:
log.error('Failed restatus: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
return True

View File

@@ -1,6 +1,5 @@
from .main import Search
def start():
return Search()

View File

@@ -1,6 +1,5 @@
from .main import Searcher
def start():
return Searcher()

View File

@@ -12,6 +12,7 @@ class SearcherBase(Plugin):
def __init__(self):
super(SearcherBase, self).__init__()
addEvent('searcher.progress', self.getProgress)
addEvent('%s.searcher.progress' % self.getType(), self.getProgress)
@@ -25,8 +26,9 @@ class SearcherBase(Plugin):
_type = self.getType()
def setCrons():
fireEvent('schedule.cron', '%s.searcher.all' % _type, self.searchAll,
day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
addEvent('app.load', setCrons)
addEvent('setting.save.%s_searcher.cron_day.after' % _type, setCrons)

View File

@@ -1,11 +1,17 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import simplifyString
from couchpotato.core.helpers.variable import splitString, removeEmpty, removeDuplicate
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
from couchpotato.core.helpers.variable import md5, getTitle, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.searcher.base import SearcherBase
from couchpotato.core.settings.model import Media, Release, ReleaseInfo
from couchpotato.environment import Env
from inspect import ismethod, isfunction
import datetime
import re
import time
import traceback
log = CPLog(__name__)
@@ -107,10 +113,10 @@ class Searcher(SearcherBase):
# Hack for older movies that don't contain quality tag
year_name = fireEvent('scanner.name_year', name, single = True)
if len(found) == 0 and movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
if size > 3000: # Assume dvdr
if size > 3000: # Assume dvdr
log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', size)
found['dvdr'] = True
else: # Assume dvdrip
else: # Assume dvdrip
log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', size)
found['dvdrip'] = True
@@ -150,12 +156,12 @@ class Searcher(SearcherBase):
try: check_names.append(max(re.findall(r'[^[]*\[([^]]*)\]', check_name), key = len).strip())
except: pass
for check_name in removeDuplicate(check_names):
for check_name in list(set(check_names)):
check_movie = fireEvent('scanner.name_year', check_name, single = True)
try:
check_words = removeEmpty(re.split('\W+', check_movie.get('name', '')))
movie_words = removeEmpty(re.split('\W+', simplifyString(movie_name)))
check_words = filter(None, re.split('\W+', check_movie.get('name', '')))
movie_words = filter(None, re.split('\W+', simplifyString(movie_name)))
if len(check_words) > 0 and len(movie_words) > 0 and len(list(set(check_words) - set(movie_words))) == 0:
return True
@@ -165,7 +171,7 @@ class Searcher(SearcherBase):
return False
def correctWords(self, rel_name, media):
media_title = fireEvent('searcher.get_search_title', media, single = True)
media_title = fireEvent('library.title', media['library'], single = True)
media_words = re.split('\W+', simplifyString(media_title))
rel_name = simplifyString(rel_name)
@@ -173,7 +179,7 @@ class Searcher(SearcherBase):
# Make sure it has required words
required_words = splitString(self.conf('required_words', section = 'searcher').lower())
try: required_words = removeDuplicate(required_words + splitString(media['category']['required'].lower()))
try: required_words = list(set(required_words + splitString(media['category']['required'].lower())))
except: pass
req_match = 0
@@ -187,7 +193,7 @@ class Searcher(SearcherBase):
# Ignore releases
ignored_words = splitString(self.conf('ignored_words', section = 'searcher').lower())
try: ignored_words = removeDuplicate(ignored_words + splitString(media['category']['ignored'].lower()))
try: ignored_words = list(set(ignored_words + splitString(media['category']['ignored'].lower())))
except: pass
ignored_match = 0

View File

@@ -1,6 +1,5 @@
from .main import MovieBase
def start():
return MovieBase()

View File

@@ -1,9 +1,8 @@
import traceback
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import splitString, tryInt, getTitle
from couchpotato.core.helpers.variable import splitString, tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.media.movie import MovieTypeBase
from couchpotato.core.settings.model import Media
@@ -62,6 +61,7 @@ class MovieBase(MovieTypeBase):
except:
pass
library = fireEvent('library.add.movie', single = True, attrs = params, update_after = update_library)
# Status
@@ -71,81 +71,68 @@ class MovieBase(MovieTypeBase):
default_profile = fireEvent('profile.default', single = True)
cat_id = params.get('category_id')
try:
db = get_session()
m = db.query(Media).filter_by(library_id = library.get('id')).first()
added = True
do_search = False
search_after = search_after and self.conf('search_on_add', section = 'moviesearcher')
if not m:
m = Media(
library_id = library.get('id'),
profile_id = params.get('profile_id', default_profile.get('id')),
status_id = status_id if status_id else status_active.get('id'),
category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None,
)
db.add(m)
db.commit()
onComplete = None
if search_after:
onComplete = self.createOnComplete(m.id)
fireEventAsync('library.update.movie', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
search_after = False
elif force_readd:
# Clean snatched history
for release in m.releases:
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id'), done_status.get('id')]:
if params.get('ignore_previous', False):
release.status_id = ignored_status.get('id')
else:
fireEvent('release.delete', release.id, single = True)
m.profile_id = params.get('profile_id', default_profile.get('id'))
m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else (m.category_id or None)
else:
log.debug('Movie already exists, not updating: %s', params)
added = False
if force_readd:
m.status_id = status_id if status_id else status_active.get('id')
m.last_edit = int(time.time())
do_search = True
db = get_session()
m = db.query(Media).filter_by(library_id = library.get('id')).first()
added = True
do_search = False
search_after = search_after and self.conf('search_on_add', section = 'moviesearcher')
if not m:
m = Media(
library_id = library.get('id'),
profile_id = params.get('profile_id', default_profile.get('id')),
status_id = status_id if status_id else status_active.get('id'),
category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None,
)
db.add(m)
db.commit()
# Remove releases
available_status = fireEvent('status.get', 'available', single = True)
for rel in m.releases:
if rel.status_id is available_status.get('id'):
db.delete(rel)
db.commit()
movie_dict = m.to_dict(self.default_dict)
if do_search and search_after:
onComplete = None
if search_after:
onComplete = self.createOnComplete(m.id)
onComplete()
if added:
if params.get('title'):
message = 'Successfully added "%s" to your wanted list.' % params.get('title', '')
else:
title = getTitle(m.library)
if title:
message = 'Successfully added "%s" to your wanted list.' % title
fireEventAsync('library.update.movie', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
search_after = False
elif force_readd:
# Clean snatched history
for release in m.releases:
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id'), done_status.get('id')]:
if params.get('ignore_previous', False):
release.status_id = ignored_status.get('id')
else:
message = 'Succesfully added to your wanted list.'
fireEvent('notify.frontend', type = 'movie.added', data = movie_dict, message = message)
fireEvent('release.delete', release.id, single = True)
return movie_dict
except:
log.error('Failed deleting media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
m.profile_id = params.get('profile_id', default_profile.get('id'))
m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else (m.category_id or None)
else:
log.debug('Movie already exists, not updating: %s', params)
added = False
if force_readd:
m.status_id = status_id if status_id else status_active.get('id')
m.last_edit = int(time.time())
do_search = True
db.commit()
# Remove releases
available_status = fireEvent('status.get', 'available', single = True)
for rel in m.releases:
if rel.status_id is available_status.get('id'):
db.delete(rel)
db.commit()
movie_dict = m.to_dict(self.default_dict)
if do_search and search_after:
onComplete = self.createOnComplete(m.id)
onComplete()
if added:
fireEvent('notify.frontend', type = 'movie.added', data = movie_dict, message = 'Successfully added "%s" to your wanted list.' % params.get('title', ''))
db.expire_all()
return movie_dict
def addView(self, **kwargs):
add_dict = self.add(params = kwargs)
@@ -157,51 +144,42 @@ class MovieBase(MovieTypeBase):
def edit(self, id = '', **kwargs):
try:
db = get_session()
db = get_session()
available_status = fireEvent('status.get', 'available', single = True)
available_status = fireEvent('status.get', 'available', single = True)
ids = splitString(id)
for media_id in ids:
ids = splitString(id)
for media_id in ids:
m = db.query(Media).filter_by(id = media_id).first()
if not m:
continue
m = db.query(Media).filter_by(id = media_id).first()
if not m:
continue
m.profile_id = kwargs.get('profile_id')
m.profile_id = kwargs.get('profile_id')
cat_id = kwargs.get('category_id')
if cat_id is not None:
m.category_id = tryInt(cat_id) if tryInt(cat_id) > 0 else None
cat_id = kwargs.get('category_id')
if cat_id is not None:
m.category_id = tryInt(cat_id) if tryInt(cat_id) > 0 else None
# Remove releases
for rel in m.releases:
if rel.status_id is available_status.get('id'):
db.delete(rel)
db.commit()
# Remove releases
for rel in m.releases:
if rel.status_id is available_status.get('id'):
db.delete(rel)
db.commit()
# Default title
if kwargs.get('default_title'):
for title in m.library.titles:
title.default = toUnicode(kwargs.get('default_title', '')).lower() == toUnicode(title.title).lower()
# Default title
if kwargs.get('default_title'):
for title in m.library.titles:
title.default = toUnicode(kwargs.get('default_title', '')).lower() == toUnicode(title.title).lower()
db.commit()
db.commit()
fireEvent('media.restatus', m.id)
fireEvent('media.restatus', m.id)
movie_dict = m.to_dict(self.default_dict)
fireEventAsync('movie.searcher.single', movie_dict, on_complete = self.createNotifyFront(media_id))
return {
'success': True,
}
except:
log.error('Failed deleting media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
movie_dict = m.to_dict(self.search_dict)
fireEventAsync('movie.searcher.single', movie_dict, on_complete = self.createNotifyFront(media_id))
db.expire_all()
return {
'success': False,
'success': True,
}

View File

@@ -36,10 +36,10 @@ var Movie = new Class({
App.on('movie.update', self.global_events['movie.update']);
// Add spinner on load / search
['media.busy', 'movie.searcher.started'].each(function(listener){
['movie.busy', 'movie.searcher.started'].each(function(listener){
self.global_events[listener] = function(notification){
if(notification.data && (self.data.id == notification.data.id || (typeOf(notification.data.id) == 'array' && notification.data.id.indexOf(self.data.id) > -1)))
self.busy(true);
if(notification.data && self.data.id == notification.data.id)
self.busy(true)
}
App.on(listener, self.global_events[listener]);
})
@@ -54,7 +54,7 @@ var Movie = new Class({
// Reload when releases have updated
self.global_events['release.update_status'] = function(notification){
var data = notification.data
if(data && self.data.id == data.movie_id){
if(data && self.data.id == data.media_id){
if(!self.data.releases)
self.data.releases = [];
@@ -329,4 +329,4 @@ var Movie = new Class({
return this.el;
}
});
});

View File

@@ -181,7 +181,7 @@ Block.Search.MovieItem = new Class({
if(categories.length == 0)
self.category_select.hide();
else {
self.category_select.show();
self.category_select.movie();
categories.each(function(category){
new Element('option', {
'value': category.data.id,

View File

@@ -1,6 +1,5 @@
from .main import MovieLibraryPlugin
def start():
return MovieLibraryPlugin()

View File

@@ -2,94 +2,111 @@ from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.library import LibraryBase
from couchpotato.core.media._base.library.base import LibraryBase
from couchpotato.core.settings.model import Library, LibraryTitle, File
from string import ascii_letters
import time
import traceback
import six
log = CPLog(__name__)
class MovieLibraryPlugin(LibraryBase):
default_dict = {'titles': {}, 'files': {}}
default_dict = {'titles': {}, 'files':{}}
def __init__(self):
addEvent('library.query', self.query)
addEvent('library.add.movie', self.add)
addEvent('library.update.movie', self.update)
addEvent('library.update.movie.release_date', self.updateReleaseDate)
def add(self, attrs = None, update_after = True):
if not attrs: attrs = {}
def query(self, library, first = True, include_year = True, **kwargs):
if library.get('type') != 'movie':
return
titles = [title['title'] for title in library['titles']]
# Add year identifier to titles
if include_year:
titles = [title + (' %s' % str(library['year'])) for title in titles]
if first:
return titles[0] if titles else None
return titles
def add(self, attrs = {}, update_after = True):
# movies don't yet contain these, so lets make sure to set defaults
type = attrs.get('type', 'movie')
primary_provider = attrs.get('primary_provider', 'imdb')
try:
db = get_session()
db = get_session()
l = db.query(Library).filter_by(identifier = attrs.get('identifier')).first()
if not l:
status = fireEvent('status.get', 'needs_update', single = True)
l = Library(
year = attrs.get('year'),
identifier = attrs.get('identifier'),
plot = toUnicode(attrs.get('plot')),
tagline = toUnicode(attrs.get('tagline')),
status_id = status.get('id'),
info = {}
)
l = db.query(Library).filter_by(type = type, identifier = attrs.get('identifier')).first()
if not l:
status = fireEvent('status.get', 'needs_update', single = True)
l = Library(
type = type,
primary_provider = primary_provider,
year = attrs.get('year'),
identifier = attrs.get('identifier'),
plot = toUnicode(attrs.get('plot')),
tagline = toUnicode(attrs.get('tagline')),
status_id = status.get('id'),
info = {},
parent = None
)
title = LibraryTitle(
title = toUnicode(attrs.get('title')),
simple_title = self.simplifyTitle(attrs.get('title')),
)
title = LibraryTitle(
title = toUnicode(attrs.get('title')),
simple_title = self.simplifyTitle(attrs.get('title')),
)
l.titles.append(title)
l.titles.append(title)
db.add(l)
db.commit()
db.add(l)
db.commit()
# Update library info
if update_after is not False:
handle = fireEventAsync if update_after is 'async' else fireEvent
handle('library.update.movie', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
# Update library info
if update_after is not False:
handle = fireEventAsync if update_after is 'async' else fireEvent
handle('library.update.movie', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
library_dict = l.to_dict(self.default_dict)
return library_dict
except:
log.error('Failed adding media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
library_dict = l.to_dict(self.default_dict)
return {}
db.expire_all()
return library_dict
def update(self, identifier, default_title = '', extended = False):
def update(self, identifier, default_title = '', force = False):
if self.shuttingDown():
return
try:
db = get_session()
db = get_session()
library = db.query(Library).filter_by(identifier = identifier).first()
done_status = fireEvent('status.get', 'done', single = True)
library = db.query(Library).filter_by(identifier = identifier).first()
done_status = fireEvent('status.get', 'done', single = True)
library_dict = None
if library:
library_dict = library.to_dict(self.default_dict)
info = fireEvent('movie.info', merge = True, extended = extended, identifier = identifier)
do_update = True
# Don't need those here
try: del info['in_wanted']
except: pass
try: del info['in_library']
except: pass
info = fireEvent('movie.info', merge = True, identifier = identifier)
if not info or len(info) == 0:
log.error('Could not update, no movie info to work with: %s', identifier)
return False
# Don't need those here
try: del info['in_wanted']
except: pass
try: del info['in_library']
except: pass
# Main info
if not info or len(info) == 0:
log.error('Could not update, no movie info to work with: %s', identifier)
return False
# Main info
if do_update:
library.plot = toUnicode(info.get('plot', ''))
library.tagline = toUnicode(info.get('tagline', ''))
library.year = info.get('year', 0)
@@ -104,17 +121,6 @@ class MovieLibraryPlugin(LibraryBase):
titles = info.get('titles', [])
log.debug('Adding titles: %s', titles)
counter = 0
def_title = None
for title in titles:
if (len(default_title) == 0 and counter == 0) or len(titles) == 1 or title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == six.u('') and toUnicode(titles[0]) == title):
def_title = toUnicode(title)
break
counter += 1
if not def_title:
def_title = toUnicode(titles[0])
for title in titles:
if not title:
continue
@@ -122,9 +128,10 @@ class MovieLibraryPlugin(LibraryBase):
t = LibraryTitle(
title = title,
simple_title = self.simplifyTitle(title),
default = title == def_title
default = (len(default_title) == 0 and counter == 0) or len(titles) == 1 or title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
)
library.titles.append(t)
counter += 1
db.commit()
@@ -146,43 +153,30 @@ class MovieLibraryPlugin(LibraryBase):
break
except:
log.debug('Failed to attach to library: %s', traceback.format_exc())
db.rollback()
library_dict = library.to_dict(self.default_dict)
return library_dict
except:
log.error('Failed update media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
return {}
db.expire_all()
return library_dict
def updateReleaseDate(self, identifier):
try:
db = get_session()
library = db.query(Library).filter_by(identifier = identifier).first()
db = get_session()
library = db.query(Library).filter_by(identifier = identifier).first()
if not library.info:
library_dict = self.update(identifier)
dates = library_dict.get('info', {}).get('release_date')
else:
dates = library.info.get('release_date')
if not library.info:
library_dict = self.update(identifier, force = True)
dates = library_dict.get('info', {}).get('release_date')
else:
dates = library.info.get('release_date')
if dates and (dates.get('expires', 0) < time.time() or dates.get('expires', 0) > time.time() + (604800 * 4)) or not dates:
dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
library.info.update({'release_date': dates})
db.commit()
if dates and (dates.get('expires', 0) < time.time() or dates.get('expires', 0) > time.time() + (604800 * 4)) or not dates:
dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
library.info.update({'release_date': dates })
db.commit()
return dates
except:
log.error('Failed updating release dates: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
return {}
db.expire_all()
return dates
def simplifyTitle(self, title):

View File

@@ -1,7 +1,6 @@
from .main import MovieSearcher
import random
def start():
return MovieSearcher()

View File

@@ -30,7 +30,6 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
addEvent('movie.searcher.try_next_release', self.tryNextRelease)
addEvent('movie.searcher.could_be_released', self.couldBeReleased)
addEvent('searcher.correct_release', self.correctRelease)
addEvent('searcher.get_search_title', self.getSearchTitle)
addApiView('movie.searcher.try_next', self.tryNextReleaseView, docs = {
'desc': 'Marks the snatched results as ignored and try the next best release',
@@ -73,21 +72,10 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
db = get_session()
movies_raw = db.query(Media).filter(
movies = db.query(Media).filter(
Media.status.has(identifier = 'active')
).all()
random.shuffle(movies_raw)
movies = []
for m in movies_raw:
movies.append(m.to_dict({
'category': {},
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files': {}},
'files': {},
}))
random.shuffle(movies)
self.in_progress = {
'total': len(movies),
@@ -98,14 +86,21 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
search_protocols = fireEvent('searcher.protocols', single = True)
for movie in movies:
movie_dict = movie.to_dict({
'category': {},
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {},
})
try:
self.single(movie, search_protocols)
self.single(movie_dict, search_protocols)
except IndexError:
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie['library']['identifier'], traceback.format_exc()))
fireEvent('library.update.movie', movie['library']['identifier'])
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
fireEvent('library.update.movie', movie_dict['library']['identifier'], force = True)
except:
log.error('Search failed for %s: %s', (movie['library']['identifier'], traceback.format_exc()))
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
self.in_progress['to_go'] -= 1
@@ -121,7 +116,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
def single(self, movie, search_protocols = None, manual = False):
# movies don't contain 'type' yet, so just set to default here
if 'type' not in movie:
if not movie.has_key('type'):
movie['type'] = 'movie'
# Find out search type
@@ -137,6 +132,8 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
log.debug('Movie doesn\'t have a profile or already done, assuming in manage tab.')
return
db = get_session()
pre_releases = fireEvent('quality.pre_releases', single = True)
release_dates = fireEvent('library.update.movie.release_date', identifier = movie['library']['identifier'], merge = True)
available_status, ignored_status, failed_status = fireEvent('status.get', ['available', 'ignored', 'failed'], single = True)
@@ -152,7 +149,6 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
fireEvent('notify.frontend', type = 'movie.searcher.started', data = {'id': movie['id']}, message = 'Searching for "%s"' % default_title)
db = get_session()
ret = False
for quality_type in movie['profile']['types']:
@@ -213,7 +209,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
if media.get('type') != 'movie': return
media_title = fireEvent('searcher.get_search_title', media, single = True)
media_title = fireEvent('library.title', media['library'], single = True)
imdb_results = kwargs.get('imdb_results', False)
retention = Env.setting('retention', section = 'nzb')
@@ -282,15 +278,13 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
now = int(time.time())
now_year = date.today().year
now_month = date.today().month
if (year is None or year < now_year - 1) and (not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0)):
return True
else:
# Don't allow movies with years to far in the future
add_year = 1 if now_month > 10 else 0 # Only allow +1 year if end of the year
if year is not None and year > (now_year + add_year):
if year is not None and year > now_year + 1:
return False
# For movies before 1972
@@ -334,7 +328,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
try:
db = get_session()
rels = db.query(Release) \
.filter_by(movie_id = media_id) \
.filter_by(media_id = media_id) \
.filter(Release.status_id.in_([snatched_status.get('id'), done_status.get('id')])) \
.all()
@@ -350,14 +344,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
except:
log.error('Failed searching for next release: %s', traceback.format_exc())
db.rollback()
return False
finally:
db.close()
def getSearchTitle(self, media):
if media['type'] == 'movie':
return getTitle(media['library'])
class SearchSetupError(Exception):
pass

View File

@@ -1,6 +1,5 @@
from .main import Suggestion
def start():
return Suggestion()

View File

@@ -1,7 +1,7 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.variable import splitString, removeDuplicate
from couchpotato.core.helpers.variable import splitString
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Media, Library
from couchpotato.environment import Env
@@ -40,7 +40,7 @@ class Suggestion(Plugin):
movies.extend(splitString(Env.prop('suggest_seen', default = '')))
suggestions = fireEvent('movie.suggest', movies = movies, ignore = ignored, single = True)
self.setCache('suggestion_cached', suggestions, timeout = 6048000) # Cache for 10 weeks
self.setCache('suggestion_cached', suggestions, timeout = 6048000) # Cache for 10 weeks
return {
'success': True,
@@ -79,10 +79,8 @@ class Suggestion(Plugin):
seen = [] if not seen else seen
if ignore_imdb:
suggested_imdbs = []
for cs in cached_suggestion:
if cs.get('imdb') != ignore_imdb and cs.get('imdb') not in suggested_imdbs:
suggested_imdbs.append(cs.get('imdb'))
if cs.get('imdb') != ignore_imdb:
new_suggestions.append(cs)
# Get new results and add them
@@ -99,7 +97,7 @@ class Suggestion(Plugin):
movies.extend(seen)
ignored.extend([x.get('imdb') for x in cached_suggestion])
suggestions = fireEvent('movie.suggest', movies = movies, ignore = removeDuplicate(ignored), single = True)
suggestions = fireEvent('movie.suggest', movies = movies, ignore = list(set(ignored)), single = True)
if suggestions:
new_suggestions.extend(suggestions)

View File

@@ -101,7 +101,7 @@ var SuggestList = new Class({
// Add rating
m.info_container.adopt(
m.rating = m.info.rating && m.info.rating.imdb && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', {
m.rating = m.info.rating && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', {
'text': parseFloat(m.info.rating.imdb[0]),
'title': parseInt(m.info.rating.imdb[1]) + ' votes'
}) : null,

View File

View File

@@ -0,0 +1,6 @@
from .main import ShowBase
def start():
return ShowBase()
config = []

View File

@@ -0,0 +1,239 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.media import MediaBase
from couchpotato.core.settings.model import Media
import time
log = CPLog(__name__)
class ShowBase(MediaBase):
_type = 'show'
def __init__(self):
super(ShowBase, self).__init__()
addApiView('show.add', self.addView, docs = {
'desc': 'Add new movie to the wanted list',
'params': {
'identifier': {'desc': 'IMDB id of the movie your want to add.'},
'profile_id': {'desc': 'ID of quality profile you want the add the movie in. If empty will use the default profile.'},
'title': {'desc': 'Movie title to use for searches. Has to be one of the titles returned by movie.search.'},
}
})
addEvent('show.add', self.add)
def addView(self, **kwargs):
add_dict = self.add(params = kwargs)
return {
'success': True if add_dict else False,
'show': add_dict,
}
def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
"""
params
{'category_id': u'-1',
'identifier': u'tt1519931',
'profile_id': u'12',
'thetvdb_id': u'158661',
'title': u'Haven'}
"""
log.debug("show.add")
# Add show parent to db first; need to update library so maps will be in place (if any)
parent = self.addToDatabase(params = params, update_library = True, type = 'show')
# TODO: add by airdate
# Add by Season/Episode numbers
self.addBySeasonEpisode(parent,
params = params,
force_readd = force_readd,
search_after = search_after,
update_library = update_library,
status_id = status_id
)
def addBySeasonEpisode(self, parent, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
identifier = params.get('id')
# 'tvdb' will always be the master for our purpose. All mapped data can be mapped
# to another source for downloading, but it will always be remapped back to tvdb numbering
# when renamed so media can be used in media players that use tvdb for info provider
#
# This currently means the episode must actually exist in tvdb in order to be found but
# the numbering can be different
#master = 'tvdb'
#destination = 'scene'
#destination = 'anidb'
#destination = 'rage'
#destination = 'trakt'
# TODO: auto mode. if anime exists use it. if scene exists use it else use tvdb
# XXX: We should abort adding show, etc if either tvdb or xem is down or we will have incorrent mappings
# I think if tvdb gets error we wont have anydata anyway, but we must make sure XEM returns!!!!
# Only the master should return results here; all other info providers should just return False
# since we are just interested in the structure at this point.
seasons = fireEvent('season.info', merge = True, identifier = identifier)
if seasons is not None:
for season in seasons:
# Make sure we are only dealing with 'tvdb' responses at this point
if season.get('primary_provider', None) != 'thetvdb':
continue
season_id = season.get('id', None)
if season_id is None: continue
season_params = {'season_identifier': season_id}
# Calling all info providers; merge your info now for individual season
single_season = fireEvent('season.info', merge = True, identifier = identifier, params = season_params)
single_season['category_id'] = params.get('category_id')
single_season['profile_id'] = params.get('profile_id')
single_season['title'] = single_season.get('original_title', None)
single_season['identifier'] = season_id
single_season['parent_identifier'] = identifier
log.info("Adding Season %s" % season_id)
s = self.addToDatabase(params = single_season, type = "season")
episode_params = {'season_identifier': season_id}
episodes = fireEvent('episode.info', merge = True, identifier = identifier, params = episode_params)
if episodes is not None:
for episode in episodes:
# Make sure we are only dealing with 'tvdb' responses at this point
if episode.get('primary_provider', None) != 'thetvdb':
continue
episode_id = episode.get('id', None)
if episode_id is None: continue
try:
episode_number = int(episode.get('episodenumber', None))
except (ValueError, TypeError):
continue
try:
absolute_number = int(episode.get('absolute_number', None))
except (ValueError, TypeError):
absolute_number = None
episode_params = {'season_identifier': season_id,
'episode_identifier': episode_id,
'episode': episode_number}
if absolute_number:
episode_params['absolute'] = absolute_number
# Calling all info providers; merge your info now for individual episode
single_episode = fireEvent('episode.info', merge = True, identifier = identifier, params = episode_params)
single_episode['category_id'] = params.get('category_id')
single_episode['profile_id'] = params.get('profile_id')
single_episode['title'] = single_episode.get('original_title', None)
single_episode['identifier'] = episode_id
single_episode['parent_identifier'] = single_season['identifier']
log.info("Adding [%sx%s] %s - %s" % (season_id,
episode_number,
params['title'],
single_episode.get('original_title', '')))
e = self.addToDatabase(params = single_episode, type = "episode")
# Start searching now that all the media has been added
if search_after:
onComplete = self.createOnComplete(parent['id'])
onComplete()
return parent
def addToDatabase(self, params = {}, type = "show", force_readd = True, search_after = False, update_library = False, status_id = None):
log.debug("show.addToDatabase")
if not params.get('identifier'):
msg = 'Can\'t add show without imdb identifier.'
log.error(msg)
fireEvent('notify.frontend', type = 'show.is_tvshow', message = msg)
return False
#else:
#try:
#is_show = fireEvent('movie.is_show', identifier = params.get('identifier'), single = True)
#if not is_show:
#msg = 'Can\'t add show, seems to be a TV show.'
#log.error(msg)
#fireEvent('notify.frontend', type = 'show.is_tvshow', message = msg)
#return False
#except:
#pass
library = fireEvent('library.add.%s' % type, single = True, attrs = params, update_after = update_library)
if not library:
return False
# Status
status_active, snatched_status, ignored_status, done_status, downloaded_status = \
fireEvent('status.get', ['active', 'snatched', 'ignored', 'done', 'downloaded'], single = True)
default_profile = fireEvent('profile.default', single = True)
cat_id = params.get('category_id', None)
db = get_session()
m = db.query(Media).filter_by(library_id = library.get('id')).first()
added = True
do_search = False
if not m:
m = Media(
type = type,
library_id = library.get('id'),
profile_id = params.get('profile_id', default_profile.get('id')),
status_id = status_id if status_id else status_active.get('id'),
category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None,
)
db.add(m)
db.commit()
onComplete = None
if search_after:
onComplete = self.createOnComplete(m.id)
fireEventAsync('library.update.%s' % type, params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
search_after = False
elif force_readd:
# Clean snatched history
for release in m.releases:
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id'), done_status.get('id')]:
if params.get('ignore_previous', False):
release.status_id = ignored_status.get('id')
else:
fireEvent('release.delete', release.id, single = True)
m.profile_id = params.get('profile_id', default_profile.get('id'))
m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None
else:
log.debug('Show already exists, not updating: %s', params)
added = False
if force_readd:
m.status_id = status_id if status_id else status_active.get('id')
m.last_edit = int(time.time())
do_search = True
db.commit()
# Remove releases
available_status = fireEvent('status.get', 'available', single = True)
for rel in m.releases:
if rel.status_id is available_status.get('id'):
db.delete(rel)
db.commit()
show_dict = m.to_dict(self.default_dict)
if do_search and search_after:
onComplete = self.createOnComplete(m.id)
onComplete()
if added:
fireEvent('notify.frontend', type = 'show.added', data = show_dict, message = 'Successfully added "%s" to your wanted list.' % params.get('title', ''))
db.expire_all()
return show_dict

View File

@@ -0,0 +1,232 @@
Block.Search.ShowItem = new Class({
Implements: [Options, Events],
initialize: function(info, options){
var self = this;
self.setOptions(options);
self.info = info;
self.alternative_titles = [];
self.create();
},
create: function(){
var self = this,
info = self.info;
self.el = new Element('div.media_result', {
'id': info.id
}).adopt(
self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', {
'src': info.images.poster[0],
'height': null,
'width': null
}) : null,
self.options_el = new Element('div.options.inlay'),
self.data_container = new Element('div.data', {
'events': {
'click': self.showOptions.bind(self)
}
}).adopt(
self.info_container = new Element('div.info').adopt(
new Element('h2').adopt(
self.title = new Element('span.title', {
'text': info.titles && info.titles.length > 0 ? info.titles[0] : 'Unknown'
}),
self.year = info.year ? new Element('span.year', {
'text': info.year
}) : null
)
)
)
)
if(info.titles)
info.titles.each(function(title){
self.alternativeTitle({
'title': title
});
})
},
alternativeTitle: function(alternative){
var self = this;
self.alternative_titles.include(alternative);
},
getTitle: function(){
var self = this;
try {
return self.info.original_title ? self.info.original_title : self.info.titles[0];
}
catch(e){
return 'Unknown';
}
},
get: function(key){
return this.info[key]
},
showOptions: function(){
var self = this;
self.createOptions();
self.data_container.addClass('open');
self.el.addEvent('outerClick', self.closeOptions.bind(self))
},
closeOptions: function(){
var self = this;
self.data_container.removeClass('open');
self.el.removeEvents('outerClick')
},
add: function(e){
var self = this;
if(e)
(e).preventDefault();
self.loadingMask();
Api.request('show.add', {
'data': {
'identifier': self.info.id,
'id': self.info.id,
'type': self.info.type,
'primary_provider': self.info.primary_provider,
'title': self.title_select.get('value'),
'profile_id': self.profile_select.get('value'),
'category_id': self.category_select.get('value')
},
'onComplete': function(json){
self.options_el.empty();
self.options_el.adopt(
new Element('div.message', {
'text': json.added ? 'Show successfully added.' : 'Show didn\'t add properly. Check logs'
})
);
self.mask.fade('out');
self.fireEvent('added');
},
'onFailure': function(){
self.options_el.empty();
self.options_el.adopt(
new Element('div.message', {
'text': 'Something went wrong, check the logs for more info.'
})
);
self.mask.fade('out');
}
});
},
createOptions: function(){
var self = this,
info = self.info;
if(!self.options_el.hasClass('set')){
if(self.info.in_library){
var in_library = [];
self.info.in_library.releases.each(function(release){
in_library.include(release.quality.label)
});
}
self.options_el.grab(
new Element('div', {
'class': self.info.in_wanted && self.info.in_wanted.profile_id || in_library ? 'in_library_wanted' : ''
}).adopt(
self.info.in_wanted && self.info.in_wanted.profile_id ? new Element('span.in_wanted', {
'text': 'Already in wanted list: ' + Quality.getProfile(self.info.in_wanted.profile_id).get('label')
}) : (in_library ? new Element('span.in_library', {
'text': 'Already in library: ' + in_library.join(', ')
}) : null),
self.title_select = new Element('select', {
'name': 'title'
}),
self.profile_select = new Element('select', {
'name': 'profile'
}),
self.category_select = new Element('select', {
'name': 'category'
}).grab(
new Element('option', {'value': -1, 'text': 'None'})
),
self.add_button = new Element('a.button', {
'text': 'Add',
'events': {
'click': self.add.bind(self)
}
})
)
);
Array.each(self.alternative_titles, function(alt){
new Element('option', {
'text': alt.title
}).inject(self.title_select)
})
// Fill categories
var categories = CategoryList.getAll();
if(categories.length == 0)
self.category_select.hide();
else {
self.category_select.show();
categories.each(function(category){
new Element('option', {
'value': category.data.id,
'text': category.data.label
}).inject(self.category_select);
});
}
// Fill profiles
var profiles = Quality.getActiveProfiles();
if(profiles.length == 1)
self.profile_select.hide();
profiles.each(function(profile){
new Element('option', {
'value': profile.id ? profile.id : profile.data.id,
'text': profile.label ? profile.label : profile.data.label
}).inject(self.profile_select)
});
self.options_el.addClass('set');
if(categories.length == 0 && self.title_select.getElements('option').length == 1 && profiles.length == 1 &&
!(self.info.in_wanted && self.info.in_wanted.profile_id || in_library))
self.add();
}
},
loadingMask: function(){
var self = this;
self.mask = new Element('div.mask').inject(self.el).fade('hide')
createSpinner(self.mask)
self.mask.fade('in')
},
toElement: function(){
return this.el
}
});

View File

@@ -0,0 +1,6 @@
from .main import EpisodeLibraryPlugin
def start():
return EpisodeLibraryPlugin()
config = []

View File

@@ -0,0 +1,266 @@
from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
from couchpotato.core.logger import CPLog
from couchpotato.core.settings.model import EpisodeLibrary, SeasonLibrary, LibraryTitle, File
from couchpotato.core.media._base.library.base import LibraryBase
from couchpotato.core.helpers.variable import tryInt
from string import ascii_letters
import time
import traceback
log = CPLog(__name__)
class EpisodeLibraryPlugin(LibraryBase):
default_dict = {'titles': {}, 'files':{}}
def __init__(self):
addEvent('library.query', self.query)
addEvent('library.identifier', self.identifier)
addEvent('library.add.episode', self.add)
addEvent('library.update.episode', self.update)
addEvent('library.update.episode_release_date', self.updateReleaseDate)
def query(self, library, first = True, condense = True, include_identifier = True, **kwargs):
if library is list or library.get('type') != 'episode':
return
# Get the titles of the season
if not library.get('related_libraries', {}).get('season', []):
log.warning('Invalid library, unable to determine title.')
return
titles = fireEvent(
'library.query',
library['related_libraries']['season'][0],
first=False,
include_identifier=include_identifier,
condense=condense,
single=True
)
identifier = fireEvent('library.identifier', library, single = True)
# Add episode identifier to titles
if include_identifier and identifier.get('episode'):
titles = [title + ('E%02d' % identifier['episode']) for title in titles]
if first:
return titles[0] if titles else None
return titles
def identifier(self, library):
if library.get('type') != 'episode':
return
identifier = {
'season': None,
'episode': None
}
scene_map = library['info'].get('map_episode', {}).get('scene')
if scene_map:
# Use scene mappings if they are available
identifier['season'] = scene_map.get('season')
identifier['episode'] = scene_map.get('episode')
else:
# Fallback to normal season/episode numbers
identifier['season'] = library.get('season_number')
identifier['episode'] = library.get('episode_number')
# Cast identifiers to integers
# TODO this will need changing to support identifiers with trailing 'a', 'b' characters
identifier['season'] = tryInt(identifier['season'], None)
identifier['episode'] = tryInt(identifier['episode'], None)
return identifier
def add(self, attrs = {}, update_after = True):
type = attrs.get('type', 'episode')
primary_provider = attrs.get('primary_provider', 'thetvdb')
db = get_session()
parent_identifier = attrs.get('parent_identifier', None)
parent = None
if parent_identifier:
parent = db.query(SeasonLibrary).filter_by(primary_provider = primary_provider, identifier = attrs.get('parent_identifier')).first()
l = db.query(EpisodeLibrary).filter_by(type = type, identifier = attrs.get('identifier')).first()
if not l:
status = fireEvent('status.get', 'needs_update', single = True)
l = EpisodeLibrary(
type = type,
primary_provider = primary_provider,
year = attrs.get('year'),
identifier = attrs.get('identifier'),
plot = toUnicode(attrs.get('plot')),
tagline = toUnicode(attrs.get('tagline')),
status_id = status.get('id'),
info = {},
parent = parent,
season_number = tryInt(attrs.get('seasonnumber', None)),
episode_number = tryInt(attrs.get('episodenumber', None)),
absolute_number = tryInt(attrs.get('absolute_number', None))
)
title = LibraryTitle(
title = toUnicode(attrs.get('title')),
simple_title = self.simplifyTitle(attrs.get('title')),
)
l.titles.append(title)
db.add(l)
db.commit()
# Update library info
if update_after is not False:
handle = fireEventAsync if update_after is 'async' else fireEvent
handle('library.update.episode', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
library_dict = l.to_dict(self.default_dict)
db.expire_all()
return library_dict
def update(self, identifier, default_title = '', force = False):
if self.shuttingDown():
return
db = get_session()
library = db.query(EpisodeLibrary).filter_by(identifier = identifier).first()
done_status = fireEvent('status.get', 'done', single = True)
if library:
library_dict = library.to_dict(self.default_dict)
do_update = True
parent_identifier = None
if library.parent is not None:
parent_identifier = library.parent.identifier
if library.status_id == done_status.get('id') and not force:
do_update = False
episode_params = {'season_identifier': parent_identifier,
'episode_identifier': identifier,
'episode': library.episode_number,
'absolute': library.absolute_number,}
info = fireEvent('episode.info', merge = True, params = episode_params)
# Don't need those here
try: del info['in_wanted']
except: pass
try: del info['in_library']
except: pass
if not info or len(info) == 0:
log.error('Could not update, no movie info to work with: %s', identifier)
return False
# Main info
if do_update:
library.plot = toUnicode(info.get('plot', ''))
library.tagline = toUnicode(info.get('tagline', ''))
library.year = info.get('year', 0)
library.status_id = done_status.get('id')
library.season_number = tryInt(info.get('seasonnumber', None))
library.episode_number = tryInt(info.get('episodenumber', None))
library.absolute_number = tryInt(info.get('absolute_number', None))
try:
library.last_updated = int(info.get('lastupdated'))
except:
library.last_updated = int(time.time())
library.info.update(info)
db.commit()
# Titles
[db.delete(title) for title in library.titles]
db.commit()
titles = info.get('titles', [])
log.debug('Adding titles: %s', titles)
counter = 0
for title in titles:
if not title:
continue
title = toUnicode(title)
t = LibraryTitle(
title = title,
simple_title = self.simplifyTitle(title),
default = (len(default_title) == 0 and counter == 0) or len(titles) == 1 or title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
)
library.titles.append(t)
counter += 1
db.commit()
# Files
images = info.get('images', [])
for image_type in ['poster']:
for image in images.get(image_type, []):
if not isinstance(image, (str, unicode)):
continue
file_path = fireEvent('file.download', url = image, single = True)
if file_path:
file_obj = fireEvent('file.add', path = file_path, type_tuple = ('image', image_type), single = True)
try:
file_obj = db.query(File).filter_by(id = file_obj.get('id')).one()
library.files.append(file_obj)
db.commit()
break
except:
log.debug('Failed to attach to library: %s', traceback.format_exc())
library_dict = library.to_dict(self.default_dict)
db.expire_all()
return library_dict
def updateReleaseDate(self, identifier):
'''XXX: Not sure what this is for yet in relation to an episode'''
pass
#db = get_session()
#library = db.query(EpisodeLibrary).filter_by(identifier = identifier).first()
#if not library.info:
#library_dict = self.update(identifier, force = True)
#dates = library_dict.get('info', {}).get('release_date')
#else:
#dates = library.info.get('release_date')
#if dates and dates.get('expires', 0) < time.time() or not dates:
#dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
#library.info.update({'release_date': dates })
#db.commit()
#db.expire_all()
#return dates
#TODO: Add to base class
def simplifyTitle(self, title):
title = toUnicode(title)
nr_prefix = '' if title[0] in ascii_letters else '#'
title = simplifyString(title)
for prefix in ['the ']:
if prefix == title[:len(prefix)]:
title = title[len(prefix):]
break
return nr_prefix + title

View File

@@ -0,0 +1,6 @@
from .main import SeasonLibraryPlugin
def start():
return SeasonLibraryPlugin()
config = []

View File

@@ -0,0 +1,242 @@
from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
from couchpotato.core.logger import CPLog
from couchpotato.core.settings.model import SeasonLibrary, ShowLibrary, LibraryTitle, File
from couchpotato.core.media._base.library.base import LibraryBase
from couchpotato.core.helpers.variable import tryInt
from string import ascii_letters
import time
import traceback
log = CPLog(__name__)
class SeasonLibraryPlugin(LibraryBase):
default_dict = {'titles': {}, 'files':{}}
def __init__(self):
addEvent('library.query', self.query)
addEvent('library.identifier', self.identifier)
addEvent('library.add.season', self.add)
addEvent('library.update.season', self.update)
addEvent('library.update.season_release_date', self.updateReleaseDate)
def query(self, library, first = True, condense = True, include_identifier = True, **kwargs):
if library is list or library.get('type') != 'season':
return
# Get the titles of the show
if not library.get('related_libraries', {}).get('show', []):
log.warning('Invalid library, unable to determine title.')
return
titles = fireEvent(
'library.query',
library['related_libraries']['show'][0],
first=False,
condense=condense,
single=True
)
# Add season map_names if they exist
if 'map_names' in library['info']:
season_names = library['info']['map_names'].get(str(library['season_number']), {})
# Add titles from all locations
# TODO only add name maps from a specific location
for location, names in season_names.items():
titles += [name for name in names if name and name not in titles]
identifier = fireEvent('library.identifier', library, single = True)
# Add season identifier to titles
if include_identifier and identifier.get('season') is not None:
titles = [title + (' S%02d' % identifier['season']) for title in titles]
if first:
return titles[0] if titles else None
return titles
def identifier(self, library):
if library.get('type') != 'season':
return
return {
'season': tryInt(library['season_number'], None)
}
def add(self, attrs = {}, update_after = True):
type = attrs.get('type', 'season')
primary_provider = attrs.get('primary_provider', 'thetvdb')
db = get_session()
parent_identifier = attrs.get('parent_identifier', None)
parent = None
if parent_identifier:
parent = db.query(ShowLibrary).filter_by(primary_provider = primary_provider, identifier = attrs.get('parent_identifier')).first()
l = db.query(SeasonLibrary).filter_by(type = type, identifier = attrs.get('identifier')).first()
if not l:
status = fireEvent('status.get', 'needs_update', single = True)
l = SeasonLibrary(
type = type,
primary_provider = primary_provider,
year = attrs.get('year'),
identifier = attrs.get('identifier'),
plot = toUnicode(attrs.get('plot')),
tagline = toUnicode(attrs.get('tagline')),
status_id = status.get('id'),
info = {},
parent = parent,
)
title = LibraryTitle(
title = toUnicode(attrs.get('title')),
simple_title = self.simplifyTitle(attrs.get('title')),
)
l.titles.append(title)
db.add(l)
db.commit()
# Update library info
if update_after is not False:
handle = fireEventAsync if update_after is 'async' else fireEvent
handle('library.update.season', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
library_dict = l.to_dict(self.default_dict)
db.expire_all()
return library_dict
def update(self, identifier, default_title = '', force = False):
if self.shuttingDown():
return
db = get_session()
library = db.query(SeasonLibrary).filter_by(identifier = identifier).first()
done_status = fireEvent('status.get', 'done', single = True)
if library:
library_dict = library.to_dict(self.default_dict)
do_update = True
parent_identifier = None
if library.parent is not None:
parent_identifier = library.parent.identifier
if library.status_id == done_status.get('id') and not force:
do_update = False
season_params = {'season_identifier': identifier}
info = fireEvent('season.info', merge = True, identifier = parent_identifier, params = season_params)
# Don't need those here
try: del info['in_wanted']
except: pass
try: del info['in_library']
except: pass
if not info or len(info) == 0:
log.error('Could not update, no movie info to work with: %s', identifier)
return False
# Main info
if do_update:
library.plot = toUnicode(info.get('plot', ''))
library.tagline = toUnicode(info.get('tagline', ''))
library.year = info.get('year', 0)
library.status_id = done_status.get('id')
library.season_number = tryInt(info.get('seasonnumber', None))
library.info.update(info)
db.commit()
# Titles
[db.delete(title) for title in library.titles]
db.commit()
titles = info.get('titles', [])
log.debug('Adding titles: %s', titles)
counter = 0
for title in titles:
if not title:
continue
title = toUnicode(title)
t = LibraryTitle(
title = title,
simple_title = self.simplifyTitle(title),
# XXX: default was None; so added a quick hack since we don't really need titiles for seasons anyway
#default = (len(default_title) == 0 and counter == 0) or len(titles) == 1 or title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
default = True,
)
library.titles.append(t)
counter += 1
db.commit()
# Files
images = info.get('images', [])
for image_type in ['poster']:
for image in images.get(image_type, []):
if not isinstance(image, (str, unicode)):
continue
file_path = fireEvent('file.download', url = image, single = True)
if file_path:
file_obj = fireEvent('file.add', path = file_path, type_tuple = ('image', image_type), single = True)
try:
file_obj = db.query(File).filter_by(id = file_obj.get('id')).one()
library.files.append(file_obj)
db.commit()
break
except:
log.debug('Failed to attach to library: %s', traceback.format_exc())
library_dict = library.to_dict(self.default_dict)
db.expire_all()
return library_dict
def updateReleaseDate(self, identifier):
'''XXX: Not sure what this is for yet in relation to a tvshow'''
pass
#db = get_session()
#library = db.query(SeasonLibrary).filter_by(identifier = identifier).first()
#if not library.info:
#library_dict = self.update(identifier, force = True)
#dates = library_dict.get('info', {}).get('release_date')
#else:
#dates = library.info.get('release_date')
#if dates and dates.get('expires', 0) < time.time() or not dates:
#dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
#library.info.update({'release_date': dates })
#db.commit()
#db.expire_all()
#return dates
#TODO: Add to base class
def simplifyTitle(self, title):
title = toUnicode(title)
nr_prefix = '' if title[0] in ascii_letters else '#'
title = simplifyString(title)
for prefix in ['the ']:
if prefix == title[:len(prefix)]:
title = title[len(prefix):]
break
return nr_prefix + title

View File

@@ -0,0 +1,6 @@
from .main import ShowLibraryPlugin
def start():
return ShowLibraryPlugin()
config = []

View File

@@ -0,0 +1,229 @@
from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
from couchpotato.core.logger import CPLog
from couchpotato.core.settings.model import ShowLibrary, LibraryTitle, File
from couchpotato.core.media._base.library.base import LibraryBase
from qcond.helpers import simplify
from qcond import QueryCondenser
from string import ascii_letters
import time
import traceback
log = CPLog(__name__)
class ShowLibraryPlugin(LibraryBase):
default_dict = {'titles': {}, 'files':{}}
def __init__(self):
self.query_condenser = QueryCondenser()
addEvent('library.query', self.query)
addEvent('library.add.show', self.add)
addEvent('library.update.show', self.update)
addEvent('library.update.show_release_date', self.updateReleaseDate)
def query(self, library, first = True, condense = True, **kwargs):
if library is list or library.get('type') != 'show':
return
titles = [title['title'] for title in library['titles']]
if condense:
# Use QueryCondenser to build a list of optimal search titles
condensed_titles = self.query_condenser.distinct(titles)
if condensed_titles:
# Use condensed titles if we got a valid result
titles = condensed_titles
else:
# Fallback to simplifying titles
titles = [simplify(title) for title in titles]
if first:
return titles[0] if titles else None
return titles
def add(self, attrs = {}, update_after = True):
type = attrs.get('type', 'show')
primary_provider = attrs.get('primary_provider', 'thetvdb')
db = get_session()
l = db.query(ShowLibrary).filter_by(type = type, identifier = attrs.get('identifier')).first()
if not l:
status = fireEvent('status.get', 'needs_update', single = True)
l = ShowLibrary(
type = type,
primary_provider = primary_provider,
year = attrs.get('year'),
identifier = attrs.get('identifier'),
plot = toUnicode(attrs.get('plot')),
tagline = toUnicode(attrs.get('tagline')),
status_id = status.get('id'),
info = {},
parent = None,
)
title = LibraryTitle(
title = toUnicode(attrs.get('title')),
simple_title = self.simplifyTitle(attrs.get('title')),
)
l.titles.append(title)
db.add(l)
db.commit()
# Update library info
if update_after is not False:
handle = fireEventAsync if update_after is 'async' else fireEvent
handle('library.update.show', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
library_dict = l.to_dict(self.default_dict)
db.expire_all()
return library_dict
def update(self, identifier, default_title = '', force = False):
if self.shuttingDown():
return
db = get_session()
library = db.query(ShowLibrary).filter_by(identifier = identifier).first()
done_status = fireEvent('status.get', 'done', single = True)
if library:
library_dict = library.to_dict(self.default_dict)
do_update = True
info = fireEvent('show.info', merge = True, identifier = identifier)
# Don't need those here
try: del info['in_wanted']
except: pass
try: del info['in_library']
except: pass
if not info or len(info) == 0:
log.error('Could not update, no show info to work with: %s', identifier)
return False
# Main info
if do_update:
library.plot = toUnicode(info.get('plot', ''))
library.tagline = toUnicode(info.get('tagline', ''))
library.year = info.get('year', 0)
library.status_id = done_status.get('id')
library.show_status = toUnicode(info.get('status', '').lower())
library.airs_time = info.get('airs_time', None)
# Bits
days_of_week_map = {
u'Monday': 1,
u'Tuesday': 2,
u'Wednesday': 4,
u'Thursday': 8,
u'Friday': 16,
u'Saturday': 32,
u'Sunday': 64,
u'Daily': 127,
}
try:
library.airs_dayofweek = days_of_week_map.get(info.get('airs_dayofweek'))
except:
library.airs_dayofweek = 0
try:
library.last_updated = int(info.get('lastupdated'))
except:
library.last_updated = int(time.time())
library.info.update(info)
db.commit()
# Titles
[db.delete(title) for title in library.titles]
db.commit()
titles = info.get('titles', [])
log.debug('Adding titles: %s', titles)
counter = 0
for title in titles:
if not title:
continue
title = toUnicode(title)
t = LibraryTitle(
title = title,
simple_title = self.simplifyTitle(title),
default = (len(default_title) == 0 and counter == 0) or len(titles) == 1 or title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
)
library.titles.append(t)
counter += 1
db.commit()
# Files
images = info.get('images', [])
for image_type in ['poster']:
for image in images.get(image_type, []):
if not isinstance(image, (str, unicode)):
continue
file_path = fireEvent('file.download', url = image, single = True)
if file_path:
file_obj = fireEvent('file.add', path = file_path, type_tuple = ('image', image_type), single = True)
try:
file_obj = db.query(File).filter_by(id = file_obj.get('id')).one()
library.files.append(file_obj)
db.commit()
break
except:
log.debug('Failed to attach to library: %s', traceback.format_exc())
library_dict = library.to_dict(self.default_dict)
db.expire_all()
return library_dict
def updateReleaseDate(self, identifier):
'''XXX: Not sure what this is for yet in relation to a show'''
pass
#db = get_session()
#library = db.query(ShowLibrary).filter_by(identifier = identifier).first()
#if not library.info:
#library_dict = self.update(identifier, force = True)
#dates = library_dict.get('info', {}).get('release_date')
#else:
#dates = library.info.get('release_date')
#if dates and dates.get('expires', 0) < time.time() or not dates:
#dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
#library.info.update({'release_date': dates })
#db.commit()
#db.expire_all()
#return dates
#TODO: Add to base class
def simplifyTitle(self, title):
title = toUnicode(title)
nr_prefix = '' if title[0] in ascii_letters else '#'
title = simplifyString(title)
for prefix in ['the ']:
if prefix == title[:len(prefix)]:
title = title[len(prefix):]
break
return nr_prefix + title

View File

@@ -0,0 +1,6 @@
from .main import ShowMatcher
def start():
return ShowMatcher()
config = []

View File

@@ -0,0 +1,127 @@
from couchpotato import CPLog
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.variable import dictIsSubset, tryInt, toIterable
from couchpotato.core.media._base.matcher.base import MatcherBase
from couchpotato.core.providers.base import MultiProvider
log = CPLog(__name__)
class ShowMatcher(MultiProvider):
def getTypes(self):
return [Season, Episode]
class Base(MatcherBase):
# TODO come back to this later, think this could be handled better, this is starting to get out of hand....
quality_map = {
'bluray_1080p': {'resolution': ['1080p'], 'source': ['bluray']},
'bluray_720p': {'resolution': ['720p'], 'source': ['bluray']},
'bdrip_1080p': {'resolution': ['1080p'], 'source': ['BDRip']},
'bdrip_720p': {'resolution': ['720p'], 'source': ['BDRip']},
'brrip_1080p': {'resolution': ['1080p'], 'source': ['BRRip']},
'brrip_720p': {'resolution': ['720p'], 'source': ['BRRip']},
'webdl_1080p': {'resolution': ['1080p'], 'source': ['webdl', ['web', 'dl']]},
'webdl_720p': {'resolution': ['720p'], 'source': ['webdl', ['web', 'dl']]},
'webdl_480p': {'resolution': ['480p'], 'source': ['webdl', ['web', 'dl']]},
'hdtv_720p': {'resolution': ['720p'], 'source': ['hdtv']},
'hdtv_sd': {'resolution': ['480p', None], 'source': ['hdtv']},
}
def __init__(self):
super(Base, self).__init__()
addEvent('%s.matcher.correct_identifier' % self.type, self.correctIdentifier)
def correct(self, chain, release, media, quality):
log.info("Checking if '%s' is valid", release['name'])
log.info2('Release parsed as: %s', chain.info)
if not fireEvent('matcher.correct_quality', chain, quality, self.quality_map, single = True):
log.info('Wrong: %s, quality does not match', release['name'])
return False
if not fireEvent('%s.matcher.correct_identifier' % self.type, chain, media):
log.info('Wrong: %s, identifier does not match', release['name'])
return False
if not fireEvent('matcher.correct_title', chain, media):
log.info("Wrong: '%s', undetermined naming.", (' '.join(chain.info['show_name'])))
return False
return True
def correctIdentifier(self, chain, media):
raise NotImplementedError()
def getChainIdentifier(self, chain):
if 'identifier' not in chain.info:
return None
identifier = self.flattenInfo(chain.info['identifier'])
# Try cast values to integers
for key, value in identifier.items():
if isinstance(value, list):
if len(value) <= 1:
value = value[0]
else:
log.warning('Wrong: identifier contains multiple season or episode values, unsupported')
return None
identifier[key] = tryInt(value, value)
return identifier
class Episode(Base):
type = 'episode'
def correctIdentifier(self, chain, media):
identifier = self.getChainIdentifier(chain)
if not identifier:
log.info2('Wrong: release identifier is not valid (unsupported or missing identifier)')
return False
# TODO - Parse episode ranges from identifier to determine if they are multi-part episodes
if any([x in identifier for x in ['episode_from', 'episode_to']]):
log.info2('Wrong: releases with identifier ranges are not supported yet')
return False
required = fireEvent('library.identifier', media['library'], single = True)
# TODO - Support air by date episodes
# TODO - Support episode parts
if identifier != required:
log.info2('Wrong: required identifier (%s) does not match release identifier (%s)', (required, identifier))
return False
return True
class Season(Base):
type = 'season'
def correctIdentifier(self, chain, media):
identifier = self.getChainIdentifier(chain)
if not identifier:
log.info2('Wrong: release identifier is not valid (unsupported or missing identifier)')
return False
# TODO - Parse episode ranges from identifier to determine if they are season packs
if any([x in identifier for x in ['episode_from', 'episode_to']]):
log.info2('Wrong: releases with identifier ranges are not supported yet')
return False
required = fireEvent('library.identifier', media['library'], single = True)
if identifier != required:
log.info2('Wrong: required identifier (%s) does not match release identifier (%s)', (required, identifier))
return False
return True

View File

@@ -0,0 +1,7 @@
from .main import ShowSearcher
import random
def start():
return ShowSearcher()
config = []

View File

@@ -0,0 +1,189 @@
from couchpotato import Env, get_session
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.variable import getTitle, toIterable
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.searcher.main import SearchSetupError
from couchpotato.core.media.show._base import ShowBase
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Media
from qcond import QueryCondenser
from qcond.helpers import simplify
log = CPLog(__name__)
class ShowSearcher(Plugin):
type = ['show', 'season', 'episode']
in_progress = False
def __init__(self):
super(ShowSearcher, self).__init__()
self.query_condenser = QueryCondenser()
for type in toIterable(self.type):
addEvent('%s.searcher.single' % type, self.single)
addEvent('searcher.correct_release', self.correctRelease)
def single(self, media, search_protocols = None, manual = False):
show, season, episode = self.getLibraries(media['library'])
db = get_session()
if media['type'] == 'show':
for library in season:
# TODO ideally we shouldn't need to fetch the media for each season library here
m = db.query(Media).filter_by(library_id = library['library_id']).first()
fireEvent('season.searcher.single', m.to_dict(ShowBase.search_dict))
return
# Find out search type
try:
if not search_protocols:
search_protocols = fireEvent('searcher.protocols', single = True)
except SearchSetupError:
return
done_status, available_status, ignored_status, failed_status = fireEvent('status.get', ['done', 'available', 'ignored', 'failed'], single = True)
if not media['profile'] or media['status_id'] == done_status.get('id'):
log.debug('Episode doesn\'t have a profile or already done, assuming in manage tab.')
return
#pre_releases = fireEvent('quality.pre_releases', single = True)
found_releases = []
too_early_to_search = []
default_title = fireEvent('library.query', media['library'], condense = False, single=True)
if not default_title:
log.error('No proper info found for episode, removing it from library to cause it from having more issues.')
#fireEvent('episode.delete', episode['id'], single = True)
return
if not show or not season:
log.error('Unable to find show or season library in database, missing required data for searching')
return
fireEvent('notify.frontend', type = 'show.searcher.started.%s' % media['id'], data = True, message = 'Searching for "%s"' % default_title)
ret = False
has_better_quality = None
for quality_type in media['profile']['types']:
# TODO check air date?
#if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']):
# too_early_to_search.append(quality_type['quality']['identifier'])
# continue
has_better_quality = 0
# See if better quality is available
for release in media['releases']:
if release['quality']['order'] <= quality_type['quality']['order'] and release['status_id'] not in [available_status.get('id'), ignored_status.get('id'), failed_status.get('id')]:
has_better_quality += 1
# Don't search for quality lower then already available.
if has_better_quality is 0:
log.info('Search for %s S%02d%s in %s', (
getTitle(show),
season['season_number'],
"E%02d" % episode['episode_number'] if episode and len(episode) == 1 else "",
quality_type['quality']['label'])
)
quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True)
results = fireEvent('searcher.search', search_protocols, media, quality, single = True)
if len(results) == 0:
log.debug('Nothing found for %s in %s', (default_title, quality_type['quality']['label']))
# Check if movie isn't deleted while searching
if not db.query(Media).filter_by(id = media.get('id')).first():
break
# Add them to this movie releases list
found_releases += fireEvent('release.create_from_search', results, media, quality_type, single = True)
# Try find a valid result and download it
if fireEvent('release.try_download_result', results, media, quality_type, manual, single = True):
ret = True
# Remove releases that aren't found anymore
for release in media.get('releases', []):
if release.get('status_id') == available_status.get('id') and release.get('identifier') not in found_releases:
fireEvent('release.delete', release.get('id'), single = True)
else:
log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title))
fireEvent('media.restatus', media['id'])
break
# Break if CP wants to shut down
if self.shuttingDown() or ret:
break
if len(too_early_to_search) > 0:
log.info2('Too early to search for %s, %s', (too_early_to_search, default_title))
elif media['type'] == 'season' and not ret and has_better_quality is 0:
# If nothing was found, start searching for episodes individually
log.info('No season pack found, starting individual episode search')
for library in episode:
# TODO ideally we shouldn't need to fetch the media for each episode library here
m = db.query(Media).filter_by(library_id = library['library_id']).first()
fireEvent('episode.searcher.single', m.to_dict(ShowBase.search_dict))
fireEvent('notify.frontend', type = 'show.searcher.ended.%s' % media['id'], data = True)
return ret
def correctRelease(self, release = None, media = None, quality = None, **kwargs):
if media.get('type') not in ['season', 'episode']: return
retention = Env.setting('retention', section = 'nzb')
if release.get('seeders') is None and 0 < retention < release.get('age', 0):
log.info2('Wrong: Outside retention, age is %s, needs %s or lower: %s', (release['age'], retention, release['name']))
return False
# Check for required and ignored words
if not fireEvent('searcher.correct_words', release['name'], media, single = True):
return False
# TODO Matching is quite costly, maybe we should be caching release matches somehow? (also look at caper optimizations)
match = fireEvent('matcher.match', release, media, quality, single = True)
if match:
return match.weight
return False
def getLibraries(self, library):
if 'related_libraries' not in library:
log.warning("'related_libraries' missing from media library, unable to continue searching")
return None, None, None
libraries = library['related_libraries']
# Show always collapses as there can never be any multiples
show = libraries.get('show', [])
show = show[0] if len(show) else None
# Season collapses if the subject is a season or episode
season = libraries.get('season', [])
if library['type'] in ['season', 'episode']:
season = season[0] if len(season) else None
# Episode collapses if the subject is a episode
episode = libraries.get('episode', [])
if library['type'] == 'episode':
episode = episode[0] if len(episode) else None
return show, season, episode

View File

@@ -13,6 +13,5 @@ def upgrade(migrate_engine):
create_column(category_column, movie)
Index('ix_movie_category_id', movie.c.category_id).create()
def downgrade(migrate_engine):
pass

View File

@@ -1,6 +1,5 @@
from .main import Boxcar
def start():
return Boxcar()

View File

@@ -1,34 +0,0 @@
from .main import Boxcar2
def start():
return Boxcar2()
config = [{
'name': 'boxcar2',
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'boxcar2',
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
},
{
'name': 'token',
'description': ('Your Boxcar access token.', 'Can be found in the app under settings')
},
{
'name': 'on_snatch',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Also send message when movie is snatched.',
},
],
}
],
}]

View File

@@ -1,39 +0,0 @@
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
log = CPLog(__name__)
class Boxcar2(Notification):
url = 'https://new.boxcar.io/api/notifications'
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
try:
message = message.strip()
long_message = ''
if listener == 'test':
long_message = 'This is a test message'
elif data.get('identifier'):
long_message = 'More movie info <a href="http://www.imdb.com/title/%s/">on IMDB</a>' % data['identifier']
data = {
'user_credentials': self.conf('token'),
'notification[title]': toUnicode(message),
'notification[long_message]': toUnicode(long_message),
}
self.urlopen(self.url, data = data)
except:
log.error('Make sure the token provided is for the correct device')
return False
log.info('Boxcar notification successful.')
return True
def isEnabled(self):
return super(Boxcar2, self).isEnabled() and self.conf('token')

View File

@@ -1,6 +1,5 @@
from .main import CoreNotifier
def start():
return CoreNotifier()

View File

@@ -67,42 +67,28 @@ class CoreNotifier(Notification):
def clean(self):
try:
db = get_session()
db.query(Notif).filter(Notif.added <= (int(time.time()) - 2419200)).delete()
db.commit()
except:
log.error('Failed cleaning notification: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
db = get_session()
db.query(Notif).filter(Notif.added <= (int(time.time()) - 2419200)).delete()
db.commit()
def markAsRead(self, ids = None, **kwargs):
ids = splitString(ids) if ids else None
try:
db = get_session()
db = get_session()
if ids:
q = db.query(Notif).filter(or_(*[Notif.id == tryInt(s) for s in ids]))
else:
q = db.query(Notif).filter_by(read = False)
if ids:
q = db.query(Notif).filter(or_(*[Notif.id == tryInt(s) for s in ids]))
else:
q = db.query(Notif).filter_by(read = False)
q.update({Notif.read: True})
db.commit()
q.update({Notif.read: True})
return {
'success': True
}
except:
log.error('Failed mark as read: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
db.commit()
return {
'success': False
'success': True
}
def listView(self, limit_offset = None, **kwargs):
@@ -154,30 +140,24 @@ class CoreNotifier(Notification):
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
try:
db = get_session()
db = get_session()
data['notification_type'] = listener if listener else 'unknown'
data['notification_type'] = listener if listener else 'unknown'
n = Notif(
message = toUnicode(message),
data = data
)
db.add(n)
db.commit()
n = Notif(
message = toUnicode(message),
data = data
)
db.add(n)
db.commit()
ndict = n.to_dict()
ndict['type'] = 'notification'
ndict['time'] = time.time()
ndict = n.to_dict()
ndict['type'] = 'notification'
ndict['time'] = time.time()
self.frontend(type = listener, data = data)
self.frontend(type = listener, data = data)
return True
except:
log.error('Failed notify: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
return True
def frontend(self, type = 'notification', data = None, message = None):
if not data: data = {}

View File

@@ -147,7 +147,7 @@ var NotificationBase = new Class({
// Process data
if(json){
Array.each(json.result, function(result){
App.trigger(result.type, [result]);
App.trigger(result.type, result);
if(result.message && result.read === undefined)
self.showMessage(result.message);
})

View File

@@ -1,6 +1,5 @@
from .main import Email
def start():
return Email()
@@ -31,7 +30,7 @@ config = [{
},
{ 'name': 'smtp_port',
'label': 'SMTP server port',
'default': '25',
'default': '25',
'type': 'int',
},
{

View File

@@ -40,7 +40,7 @@ class Email(Notification):
log.debug("SMTP over SSL %s", ("enabled" if ssl == 1 else "disabled"))
mailserver = smtplib.SMTP_SSL(smtp_server) if ssl == 1 else smtplib.SMTP(smtp_server)
if starttls:
if (starttls):
log.debug("Using StartTLS to initiate the connection with the SMTP server")
mailserver.starttls()

View File

@@ -1,6 +1,5 @@
from .main import Growl
def start():
return Growl()

View File

@@ -37,7 +37,7 @@ class Growl(Notification):
)
self.growl.register()
self.registered = True
except Exception as e:
except Exception, e:
if 'timed out' in str(e):
self.registered = True
else:

View File

@@ -1,6 +1,5 @@
from .main import NMJ
def start():
return NMJ()

View File

@@ -86,17 +86,18 @@ class NMJ(Notification):
'arg3': '',
}
params = tryUrlencode(params)
update_url = 'http://%(host)s:8008/metadata_database?%(params)s' % {'host': host, 'params': params}
UPDATE_URL = 'http://%(host)s:8008/metadata_database?%(params)s'
updateUrl = UPDATE_URL % {'host': host, 'params': params}
try:
response = self.urlopen(update_url)
response = self.urlopen(updateUrl)
except:
return False
try:
et = etree.fromstring(response)
result = et.findtext('returnValue')
except SyntaxError as e:
except SyntaxError, e:
log.error('Unable to parse XML returned from the Popcorn Hour: %s', e)
return False

View File

@@ -1,6 +1,5 @@
from .main import NotifyMyAndroid
def start():
return NotifyMyAndroid()

View File

@@ -2,7 +2,6 @@ from couchpotato.core.helpers.variable import splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
import pynma
import six
log = CPLog(__name__)
@@ -27,7 +26,7 @@ class NotifyMyAndroid(Notification):
successful = 0
for key in keys:
if not response[str(key)]['code'] == six.u('200'):
if not response[str(key)]['code'] == u'200':
log.error('Could not send notification to NotifyMyAndroid (%s). %s', (key, response[key]['message']))
else:
successful += 1

View File

@@ -1,6 +1,5 @@
from .main import NotifyMyWP
def start():
return NotifyMyWP()

View File

@@ -2,15 +2,13 @@ from couchpotato.core.helpers.variable import splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from pynmwp import PyNMWP
import six
log = CPLog(__name__)
class NotifyMyWP(Notification):
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
def notify(self, message = '', data = {}, listener = None):
keys = splitString(self.conf('api_key'))
p = PyNMWP(keys, self.conf('dev_key'))
@@ -18,7 +16,7 @@ class NotifyMyWP(Notification):
response = p.push(application = self.default_title, event = message, description = message, priority = self.conf('priority'), batch_mode = len(keys) > 1)
for key in keys:
if not response[key]['Code'] == six.u('200'):
if not response[key]['Code'] == u'200':
log.error('Could not send notification to NotifyMyWindowsPhone (%s). %s', (key, response[key]['message']))
return False

View File

@@ -1,6 +1,5 @@
from .main import Plex
def start():
return Plex()

View File

@@ -29,7 +29,7 @@ class PlexClientHTTP(PlexClientProtocol):
try:
self.plex.urlopen(url, headers = headers, timeout = 3, show_error = False)
except Exception as err:
except Exception, err:
log.error("Couldn't sent command to Plex: %s", err)
return False
@@ -68,7 +68,7 @@ class PlexClientJSON(PlexClientProtocol):
try:
requests.post(url, headers = headers, timeout = 3, data = json.dumps(request))
except Exception as err:
except Exception, err:
log.error("Couldn't sent command to Plex: %s", err)
return False

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