Merge branch 'tv' of https://github.com/RuudBurger/CouchPotatoServer into tv_database
This commit is contained in:
@@ -31,6 +31,13 @@ class Loader(object):
|
||||
if os.path.isdir(path) and provider[:2] != '__':
|
||||
self.paths[provider + '_provider'] = (25, 'couchpotato.core.providers.' + provider, path)
|
||||
|
||||
# Add media to loader
|
||||
media_dir = os.path.join(root, 'couchpotato', 'core', 'media')
|
||||
for media in os.listdir(media_dir):
|
||||
path = os.path.join(media_dir, media)
|
||||
if os.path.isdir(path) and media[:2] != '__':
|
||||
self.paths[media + '_media'] = (25, 'couchpotato.core.media.' + media, path)
|
||||
|
||||
|
||||
for plugin_type, plugin_tuple in self.paths.iteritems():
|
||||
priority, module, dir_name = plugin_tuple
|
||||
|
||||
17
couchpotato/core/media/__init__.py
Normal file
17
couchpotato/core/media/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class MediaBase(Plugin):
|
||||
|
||||
identifier = None
|
||||
|
||||
def __init__(self):
|
||||
|
||||
addEvent('media.types', self.getType)
|
||||
|
||||
def getType(self):
|
||||
return self.identifier
|
||||
0
couchpotato/core/media/_base/__init__.py
Normal file
0
couchpotato/core/media/_base/__init__.py
Normal file
@@ -11,8 +11,8 @@ config = [{
|
||||
{
|
||||
'tab': 'searcher',
|
||||
'name': 'searcher',
|
||||
'label': 'Search',
|
||||
'description': 'Options for the searchers',
|
||||
'label': 'Basics',
|
||||
'description': 'General search options',
|
||||
'options': [
|
||||
{
|
||||
'name': 'preferred_method',
|
||||
@@ -22,14 +22,6 @@ config = [{
|
||||
'type': 'dropdown',
|
||||
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
|
||||
},
|
||||
{
|
||||
'name': 'always_search',
|
||||
'default': False,
|
||||
'advanced': True,
|
||||
'type': 'bool',
|
||||
'label': 'Always search',
|
||||
'description': 'Search for movies even before there is a ETA. Enabling this will probably get you a lot of fakes.',
|
||||
},
|
||||
],
|
||||
}, {
|
||||
'tab': 'searcher',
|
||||
@@ -60,46 +52,6 @@ config = [{
|
||||
'description': 'Ignores releases that match any of these sets. (Works like explained above)'
|
||||
},
|
||||
],
|
||||
}, {
|
||||
'tab': 'searcher',
|
||||
'name': 'cronjob',
|
||||
'label': 'Cronjob',
|
||||
'advanced': True,
|
||||
'description': 'Cron settings for the searcher see: <a href="http://packages.python.org/APScheduler/cronschedule.html">APScheduler</a> for details.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'run_on_launch',
|
||||
'label': 'Run on launch',
|
||||
'advanced': True,
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'description': 'Force run the searcher after (re)start.',
|
||||
},
|
||||
{
|
||||
'name': 'cron_day',
|
||||
'label': 'Day',
|
||||
'advanced': True,
|
||||
'default': '*',
|
||||
'type': 'string',
|
||||
'description': '<strong>*</strong>: Every day, <strong>*/2</strong>: Every 2 days, <strong>1</strong>: Every first of the month.',
|
||||
},
|
||||
{
|
||||
'name': 'cron_hour',
|
||||
'label': 'Hour',
|
||||
'advanced': True,
|
||||
'default': random.randint(0, 23),
|
||||
'type': 'string',
|
||||
'description': '<strong>*</strong>: Every hour, <strong>*/8</strong>: Every 8 hours, <strong>3</strong>: At 3, midnight.',
|
||||
},
|
||||
{
|
||||
'name': 'cron_minute',
|
||||
'label': 'Minute',
|
||||
'advanced': True,
|
||||
'default': random.randint(0, 59),
|
||||
'type': 'string',
|
||||
'description': "Just keep it random, so the providers don't get DDOSed by every CP user on a 'full' hour."
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}, {
|
||||
207
couchpotato/core/media/_base/searcher/main.py
Normal file
207
couchpotato/core/media/_base/searcher/main.py
Normal file
@@ -0,0 +1,207 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||
from couchpotato.core.helpers.variable import md5, getTitle
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
||||
from couchpotato.environment import Env
|
||||
from inspect import ismethod, isfunction
|
||||
import datetime
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Searcher(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('searcher.get_types', self.getSearchTypes)
|
||||
addEvent('searcher.contains_other_quality', self.containsOtherQuality)
|
||||
addEvent('searcher.correct_year', self.correctYear)
|
||||
addEvent('searcher.correct_name', self.correctName)
|
||||
addEvent('searcher.download', self.download)
|
||||
|
||||
def download(self, data, movie, manual = False):
|
||||
|
||||
# Test to see if any downloaders are enabled for this type
|
||||
downloader_enabled = fireEvent('download.enabled', manual, data, single = True)
|
||||
|
||||
if downloader_enabled:
|
||||
|
||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||
|
||||
# Download movie to temp
|
||||
filedata = None
|
||||
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
|
||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
if filedata == 'try_next':
|
||||
return filedata
|
||||
|
||||
download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
||||
log.debug('Downloader result: %s', download_result)
|
||||
|
||||
if download_result:
|
||||
try:
|
||||
# Mark release as snatched
|
||||
db = get_session()
|
||||
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
|
||||
if rls:
|
||||
renamer_enabled = Env.setting('enabled', 'renamer')
|
||||
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
|
||||
|
||||
# Save download-id info if returned
|
||||
if isinstance(download_result, dict):
|
||||
for key in download_result:
|
||||
rls_info = ReleaseInfo(
|
||||
identifier = 'download_%s' % key,
|
||||
value = toUnicode(download_result.get(key))
|
||||
)
|
||||
rls.info.append(rls_info)
|
||||
db.commit()
|
||||
|
||||
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
|
||||
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
|
||||
log.info(snatch_message)
|
||||
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
|
||||
|
||||
# If renamer isn't used, mark movie done
|
||||
if not renamer_enabled:
|
||||
active_status = fireEvent('status.get', 'active', single = True)
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
try:
|
||||
if movie['status_id'] == active_status.get('id'):
|
||||
for profile_type in movie['profile']['types']:
|
||||
if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
|
||||
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
|
||||
|
||||
# Mark release done
|
||||
rls.status_id = done_status.get('id')
|
||||
rls.last_edit = int(time.time())
|
||||
db.commit()
|
||||
|
||||
# Mark movie done
|
||||
mvie = db.query(Movie).filter_by(id = movie['id']).first()
|
||||
mvie.status_id = done_status.get('id')
|
||||
mvie.last_edit = int(time.time())
|
||||
db.commit()
|
||||
except:
|
||||
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
|
||||
|
||||
except:
|
||||
log.error('Failed marking movie finished: %s', traceback.format_exc())
|
||||
|
||||
return True
|
||||
|
||||
log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('type', '')))
|
||||
|
||||
return False
|
||||
|
||||
def getSearchTypes(self):
|
||||
|
||||
download_types = fireEvent('download.enabled_types', merge = True)
|
||||
provider_types = fireEvent('provider.enabled_types', merge = True)
|
||||
|
||||
if download_types and len(list(set(provider_types) & set(download_types))) == 0:
|
||||
log.error('There aren\'t any providers enabled for your downloader (%s). Check your settings.', ','.join(download_types))
|
||||
return []
|
||||
|
||||
for useless_provider in list(set(provider_types) - set(download_types)):
|
||||
log.debug('Provider for "%s" enabled, but no downloader.', useless_provider)
|
||||
|
||||
search_types = download_types
|
||||
|
||||
if len(search_types) == 0:
|
||||
log.error('There aren\'t any downloaders enabled. Please pick one in settings.')
|
||||
return []
|
||||
|
||||
return search_types
|
||||
|
||||
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}):
|
||||
|
||||
name = nzb['name']
|
||||
size = nzb.get('size', 0)
|
||||
nzb_words = re.split('\W+', simplifyString(name))
|
||||
|
||||
qualities = fireEvent('quality.all', single = True)
|
||||
|
||||
found = {}
|
||||
for quality in qualities:
|
||||
# Main in words
|
||||
if quality['identifier'] in nzb_words:
|
||||
found[quality['identifier']] = True
|
||||
|
||||
# Alt in words
|
||||
if list(set(nzb_words) & set(quality['alternative'])):
|
||||
found[quality['identifier']] = True
|
||||
|
||||
# Try guessing via quality tags
|
||||
guess = fireEvent('quality.guess', [nzb.get('name')], single = True)
|
||||
if guess:
|
||||
found[guess['identifier']] = True
|
||||
|
||||
# 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
|
||||
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
|
||||
log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', (size))
|
||||
found['dvdrip'] = True
|
||||
|
||||
# Allow other qualities
|
||||
for allowed in preferred_quality.get('allow'):
|
||||
if found.get(allowed):
|
||||
del found[allowed]
|
||||
|
||||
return not (found.get(preferred_quality['identifier']) and len(found) == 1)
|
||||
|
||||
def correctYear(self, haystack, year, year_range):
|
||||
|
||||
if not isinstance(haystack, (list, tuple, set)):
|
||||
haystack = [haystack]
|
||||
|
||||
for string in haystack:
|
||||
|
||||
year_name = fireEvent('scanner.name_year', string, single = True)
|
||||
|
||||
if year_name and ((year - year_range) <= year_name.get('year') <= (year + year_range)):
|
||||
log.debug('Movie year matches range: %s looking for %s', (year_name.get('year'), year))
|
||||
return True
|
||||
|
||||
log.debug('Movie year doesn\'t matche range: %s looking for %s', (year_name.get('year'), year))
|
||||
return False
|
||||
|
||||
def correctName(self, check_name, movie_name):
|
||||
|
||||
check_names = [check_name]
|
||||
|
||||
# Match names between "
|
||||
try: check_names.append(re.search(r'([\'"])[^\1]*\1', check_name).group(0))
|
||||
except: pass
|
||||
|
||||
# Match longest name between []
|
||||
try: check_names.append(max(check_name.split('['), key = len))
|
||||
except: pass
|
||||
|
||||
for check_name in list(set(check_names)):
|
||||
check_movie = fireEvent('scanner.name_year', check_name, single = True)
|
||||
|
||||
try:
|
||||
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
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
class SearchSetupError(Exception):
|
||||
pass
|
||||
0
couchpotato/core/media/movie/__init__.py
Normal file
0
couchpotato/core/media/movie/__init__.py
Normal file
6
couchpotato/core/media/movie/_base/__init__.py
Normal file
6
couchpotato/core/media/movie/_base/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .main import MovieBase
|
||||
|
||||
def start():
|
||||
return MovieBase()
|
||||
|
||||
config = []
|
||||
@@ -4,7 +4,7 @@ from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
||||
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.media import MediaBase
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, Movie, \
|
||||
Release
|
||||
from couchpotato.environment import Env
|
||||
@@ -16,7 +16,9 @@ import time
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class MoviePlugin(Plugin):
|
||||
class MovieBase(MediaBase):
|
||||
|
||||
identifier = 'movie'
|
||||
|
||||
default_dict = {
|
||||
'profile': {'types': {'quality': {}}},
|
||||
@@ -27,6 +29,8 @@ class MoviePlugin(Plugin):
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super(MovieBase, self).__init__()
|
||||
|
||||
addApiView('movie.search', self.search, docs = {
|
||||
'desc': 'Search the movie providers for a movie',
|
||||
'params': {
|
||||
@@ -476,7 +480,7 @@ class MoviePlugin(Plugin):
|
||||
fireEvent('movie.restatus', m.id)
|
||||
|
||||
movie_dict = m.to_dict(self.default_dict)
|
||||
fireEventAsync('searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id))
|
||||
fireEventAsync('movie.searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id))
|
||||
|
||||
db.expire_all()
|
||||
return {
|
||||
@@ -574,7 +578,7 @@ class MoviePlugin(Plugin):
|
||||
def onComplete():
|
||||
db = get_session()
|
||||
movie = db.query(Movie).filter_by(id = movie_id).first()
|
||||
fireEventAsync('searcher.single', movie.to_dict(self.default_dict), on_complete = self.createNotifyFront(movie_id))
|
||||
fireEventAsync('movie.searcher.single', movie.to_dict(self.default_dict), on_complete = self.createNotifyFront(movie_id))
|
||||
db.expire_all()
|
||||
|
||||
return onComplete
|
||||
@@ -29,14 +29,14 @@ var Movie = new Class({
|
||||
self.update.delay(2000, self, notification);
|
||||
});
|
||||
|
||||
['movie.busy', 'searcher.started'].each(function(listener){
|
||||
['movie.busy', 'movie.searcher.started'].each(function(listener){
|
||||
App.addEvent(listener+'.'+self.data.id, function(notification){
|
||||
if(notification.data)
|
||||
self.busy(true)
|
||||
});
|
||||
})
|
||||
|
||||
App.addEvent('searcher.ended.'+self.data.id, function(notification){
|
||||
App.addEvent('movie.searcher.ended.'+self.data.id, function(notification){
|
||||
if(notification.data)
|
||||
self.busy(false)
|
||||
});
|
||||
@@ -53,7 +53,7 @@ var Movie = new Class({
|
||||
|
||||
// Remove events
|
||||
App.removeEvents('movie.update.'+self.data.id);
|
||||
['movie.busy', 'searcher.started'].each(function(listener){
|
||||
['movie.busy', 'movie.searcher.started'].each(function(listener){
|
||||
App.removeEvents(listener+'.'+self.data.id);
|
||||
})
|
||||
},
|
||||
60
couchpotato/core/media/movie/searcher/__init__.py
Normal file
60
couchpotato/core/media/movie/searcher/__init__.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from .main import MovieSearcher
|
||||
import random
|
||||
|
||||
def start():
|
||||
return MovieSearcher()
|
||||
|
||||
config = [{
|
||||
'name': 'searcher',
|
||||
'order': 20,
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'searcher',
|
||||
'name': 'movie_searcher',
|
||||
'label': 'Movie search',
|
||||
'description': 'Search options for movies',
|
||||
'advanced': True,
|
||||
'options': [
|
||||
{
|
||||
'name': 'always_search',
|
||||
'default': False,
|
||||
'type': 'bool',
|
||||
'label': 'Always search',
|
||||
'description': 'Search for movies even before there is a ETA. Enabling this will probably get you a lot of fakes.',
|
||||
},
|
||||
{
|
||||
'name': 'run_on_launch',
|
||||
'label': 'Run on launch',
|
||||
'advanced': True,
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'description': 'Force run the searcher after (re)start.',
|
||||
},
|
||||
{
|
||||
'name': 'cron_day',
|
||||
'label': 'Day',
|
||||
'advanced': True,
|
||||
'default': '*',
|
||||
'type': 'string',
|
||||
'description': '<strong>*</strong>: Every day, <strong>*/2</strong>: Every 2 days, <strong>1</strong>: Every first of the month. See <a href="http://packages.python.org/APScheduler/cronschedule.html">APScheduler</a> for details.',
|
||||
},
|
||||
{
|
||||
'name': 'cron_hour',
|
||||
'label': 'Hour',
|
||||
'advanced': True,
|
||||
'default': random.randint(0, 23),
|
||||
'type': 'string',
|
||||
'description': '<strong>*</strong>: Every hour, <strong>*/8</strong>: Every 8 hours, <strong>3</strong>: At 3, midnight.',
|
||||
},
|
||||
{
|
||||
'name': 'cron_minute',
|
||||
'label': 'Minute',
|
||||
'advanced': True,
|
||||
'default': random.randint(0, 59),
|
||||
'type': 'string',
|
||||
'description': "Just keep it random, so the providers don't get DDOSed by every CP user on a 'full' hour."
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
@@ -3,13 +3,12 @@ from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||
from couchpotato.core.helpers.variable import md5, getTitle, splitString, \
|
||||
possibleTitles
|
||||
possibleTitles, getImdb
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
||||
from couchpotato.environment import Env
|
||||
from datetime import date
|
||||
from inspect import ismethod, isfunction
|
||||
from sqlalchemy.exc import InterfaceError
|
||||
import datetime
|
||||
import random
|
||||
@@ -20,30 +19,29 @@ import traceback
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Searcher(Plugin):
|
||||
class MovieSearcher(Plugin):
|
||||
|
||||
in_progress = False
|
||||
|
||||
def __init__(self):
|
||||
addEvent('searcher.all', self.allMovies)
|
||||
addEvent('searcher.single', self.single)
|
||||
addEvent('searcher.correct_movie', self.correctMovie)
|
||||
addEvent('searcher.download', self.download)
|
||||
addEvent('searcher.try_next_release', self.tryNextRelease)
|
||||
addEvent('searcher.could_be_released', self.couldBeReleased)
|
||||
addEvent('movie.searcher.all', self.searchAll)
|
||||
addEvent('movie.searcher.single', self.single)
|
||||
addEvent('movie.searcher.correct_movie', self.correctMovie)
|
||||
addEvent('movie.searcher.try_next_release', self.tryNextRelease)
|
||||
addEvent('movie.searcher.could_be_released', self.couldBeReleased)
|
||||
|
||||
addApiView('searcher.try_next', self.tryNextReleaseView, docs = {
|
||||
addApiView('movie.searcher.try_next', self.tryNextReleaseView, docs = {
|
||||
'desc': 'Marks the snatched results as ignored and try the next best release',
|
||||
'params': {
|
||||
'id': {'desc': 'The id of the movie'},
|
||||
},
|
||||
})
|
||||
|
||||
addApiView('searcher.full_search', self.allMoviesView, docs = {
|
||||
addApiView('movie.searcher.full_search', self.searchAllView, docs = {
|
||||
'desc': 'Starts a full search for all wanted movies',
|
||||
})
|
||||
|
||||
addApiView('searcher.progress', self.getProgress, docs = {
|
||||
addApiView('movie.searcher.progress', self.getProgress, docs = {
|
||||
'desc': 'Get the progress of current full search',
|
||||
'return': {'type': 'object', 'example': """{
|
||||
'progress': False || object, total & to_go,
|
||||
@@ -51,7 +49,7 @@ class Searcher(Plugin):
|
||||
})
|
||||
|
||||
if self.conf('run_on_launch'):
|
||||
addEvent('app.load', self.allMovies)
|
||||
addEvent('app.load', self.searchAll)
|
||||
|
||||
addEvent('app.load', self.setCrons)
|
||||
addEvent('setting.save.searcher.cron_day.after', self.setCrons)
|
||||
@@ -59,16 +57,16 @@ class Searcher(Plugin):
|
||||
addEvent('setting.save.searcher.cron_minute.after', self.setCrons)
|
||||
|
||||
def setCrons(self):
|
||||
fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
||||
fireEvent('schedule.cron', 'movie.searcher.all', self.searchAll, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
||||
|
||||
def allMoviesView(self, **kwargs):
|
||||
def searchAllView(self, **kwargs):
|
||||
|
||||
in_progress = self.in_progress
|
||||
if not in_progress:
|
||||
fireEventAsync('searcher.all')
|
||||
fireEvent('notify.frontend', type = 'searcher.started', data = True, message = 'Full search started')
|
||||
fireEventAsync('movie.searcher.all')
|
||||
fireEvent('notify.frontend', type = 'movie.searcher.started', data = True, message = 'Full search started')
|
||||
else:
|
||||
fireEvent('notify.frontend', type = 'searcher.already_started', data = True, message = 'Full search already in progress')
|
||||
fireEvent('notify.frontend', type = 'movie.searcher.already_started', data = True, message = 'Full search already in progress')
|
||||
|
||||
return {
|
||||
'success': not in_progress
|
||||
@@ -80,7 +78,7 @@ class Searcher(Plugin):
|
||||
'progress': self.in_progress
|
||||
}
|
||||
|
||||
def allMovies(self):
|
||||
def searchAll(self):
|
||||
|
||||
if self.in_progress:
|
||||
log.info('Search already in progress')
|
||||
@@ -101,7 +99,7 @@ class Searcher(Plugin):
|
||||
}
|
||||
|
||||
try:
|
||||
search_types = self.getSearchTypes()
|
||||
search_types = fireEvent('searcher.get_types', single = True)
|
||||
|
||||
for movie in movies:
|
||||
movie_dict = movie.to_dict({
|
||||
@@ -136,7 +134,7 @@ class Searcher(Plugin):
|
||||
# Find out search type
|
||||
try:
|
||||
if not search_types:
|
||||
search_types = self.getSearchTypes()
|
||||
search_types = fireEvent('searcher.get_types', single = True)
|
||||
except SearchSetupError:
|
||||
return
|
||||
|
||||
@@ -161,7 +159,7 @@ class Searcher(Plugin):
|
||||
fireEvent('movie.delete', movie['id'], single = True)
|
||||
return
|
||||
|
||||
fireEvent('notify.frontend', type = 'searcher.started.%s' % movie['id'], data = True, message = 'Searching for "%s"' % default_title)
|
||||
fireEvent('notify.frontend', type = 'movie.searcher.started.%s' % movie['id'], data = True, message = 'Searching for "%s"' % default_title)
|
||||
|
||||
|
||||
ret = False
|
||||
@@ -253,7 +251,7 @@ class Searcher(Plugin):
|
||||
log.info('Ignored, score to low: %s', nzb['name'])
|
||||
continue
|
||||
|
||||
downloaded = self.download(data = nzb, movie = movie)
|
||||
downloaded = fireEvent('searcher.download', data = nzb, movie = movie, single = True)
|
||||
if downloaded is True:
|
||||
ret = True
|
||||
break
|
||||
@@ -277,107 +275,10 @@ class Searcher(Plugin):
|
||||
if len(too_early_to_search) > 0:
|
||||
log.info2('Too early to search for %s, %s', (too_early_to_search, default_title))
|
||||
|
||||
fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True)
|
||||
fireEvent('notify.frontend', type = 'movie.searcher.ended.%s' % movie['id'], data = True)
|
||||
|
||||
return ret
|
||||
|
||||
def download(self, data, movie, manual = False):
|
||||
|
||||
# Test to see if any downloaders are enabled for this type
|
||||
downloader_enabled = fireEvent('download.enabled', manual, data, single = True)
|
||||
|
||||
if downloader_enabled:
|
||||
|
||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||
|
||||
# Download movie to temp
|
||||
filedata = None
|
||||
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
|
||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
if filedata == 'try_next':
|
||||
return filedata
|
||||
|
||||
download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
||||
log.debug('Downloader result: %s', download_result)
|
||||
|
||||
if download_result:
|
||||
try:
|
||||
# Mark release as snatched
|
||||
db = get_session()
|
||||
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
|
||||
if rls:
|
||||
renamer_enabled = Env.setting('enabled', 'renamer')
|
||||
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
|
||||
|
||||
# Save download-id info if returned
|
||||
if isinstance(download_result, dict):
|
||||
for key in download_result:
|
||||
rls_info = ReleaseInfo(
|
||||
identifier = 'download_%s' % key,
|
||||
value = toUnicode(download_result.get(key))
|
||||
)
|
||||
rls.info.append(rls_info)
|
||||
db.commit()
|
||||
|
||||
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
|
||||
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
|
||||
log.info(snatch_message)
|
||||
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
|
||||
|
||||
# If renamer isn't used, mark movie done
|
||||
if not renamer_enabled:
|
||||
active_status = fireEvent('status.get', 'active', single = True)
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
try:
|
||||
if movie['status_id'] == active_status.get('id'):
|
||||
for profile_type in movie['profile']['types']:
|
||||
if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
|
||||
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
|
||||
|
||||
# Mark release done
|
||||
rls.status_id = done_status.get('id')
|
||||
rls.last_edit = int(time.time())
|
||||
db.commit()
|
||||
|
||||
# Mark movie done
|
||||
mvie = db.query(Movie).filter_by(id = movie['id']).first()
|
||||
mvie.status_id = done_status.get('id')
|
||||
mvie.last_edit = int(time.time())
|
||||
db.commit()
|
||||
except:
|
||||
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
|
||||
|
||||
except:
|
||||
log.error('Failed marking movie finished: %s', traceback.format_exc())
|
||||
|
||||
return True
|
||||
|
||||
log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('type', '')))
|
||||
|
||||
return False
|
||||
|
||||
def getSearchTypes(self):
|
||||
|
||||
download_types = fireEvent('download.enabled_types', merge = True)
|
||||
provider_types = fireEvent('provider.enabled_types', merge = True)
|
||||
|
||||
if download_types and len(list(set(provider_types) & set(download_types))) == 0:
|
||||
log.error('There aren\'t any providers enabled for your downloader (%s). Check your settings.', ','.join(download_types))
|
||||
raise NoProviders
|
||||
|
||||
for useless_provider in list(set(provider_types) - set(download_types)):
|
||||
log.debug('Provider for "%s" enabled, but no downloader.', useless_provider)
|
||||
|
||||
search_types = download_types
|
||||
|
||||
if len(search_types) == 0:
|
||||
log.error('There aren\'t any downloaders enabled. Please pick one in settings.')
|
||||
raise NoDownloaders
|
||||
|
||||
return search_types
|
||||
|
||||
def correctMovie(self, nzb = None, movie = None, quality = None, **kwargs):
|
||||
|
||||
imdb_results = kwargs.get('imdb_results', False)
|
||||
@@ -430,7 +331,7 @@ class Searcher(Plugin):
|
||||
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
||||
|
||||
# Contains lower quality string
|
||||
if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality):
|
||||
if fireEvent('searcher.contains_other_quality', nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality, single = True):
|
||||
log.info2('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
|
||||
return False
|
||||
|
||||
@@ -460,20 +361,20 @@ class Searcher(Plugin):
|
||||
return True
|
||||
|
||||
# Check if nzb contains imdb link
|
||||
if self.checkIMDB([nzb.get('description', '')], movie['library']['identifier']):
|
||||
if getImdb(nzb.get('description', '')) == movie['library']['identifier']:
|
||||
return True
|
||||
|
||||
for raw_title in movie['library']['titles']:
|
||||
for movie_title in possibleTitles(raw_title['title']):
|
||||
movie_words = re.split('\W+', simplifyString(movie_title))
|
||||
|
||||
if self.correctName(nzb['name'], movie_title):
|
||||
if fireEvent('searcher.correct_name', nzb['name'], movie_title, single = True):
|
||||
# if no IMDB link, at least check year range 1
|
||||
if len(movie_words) > 2 and self.correctYear([nzb['name']], movie['library']['year'], 1):
|
||||
if len(movie_words) > 2 and fireEvent('searcher.correct_year', nzb['name'], movie['library']['year'], 1, single = True):
|
||||
return True
|
||||
|
||||
# if no IMDB link, at least check year
|
||||
if len(movie_words) <= 2 and self.correctYear([nzb['name']], movie['library']['year'], 0):
|
||||
if len(movie_words) <= 2 and fireEvent('searcher.correct_year', nzb['name'], movie['library']['year'], 0, single = True):
|
||||
return True
|
||||
|
||||
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'", (nzb['name'], movie_name, movie['library']['year']))
|
||||
@@ -527,45 +428,6 @@ class Searcher(Plugin):
|
||||
|
||||
return False
|
||||
|
||||
def correctYear(self, haystack, year, year_range):
|
||||
|
||||
for string in haystack:
|
||||
|
||||
year_name = fireEvent('scanner.name_year', string, single = True)
|
||||
|
||||
if year_name and ((year - year_range) <= year_name.get('year') <= (year + year_range)):
|
||||
log.debug('Movie year matches range: %s looking for %s', (year_name.get('year'), year))
|
||||
return True
|
||||
|
||||
log.debug('Movie year doesn\'t matche range: %s looking for %s', (year_name.get('year'), year))
|
||||
return False
|
||||
|
||||
def correctName(self, check_name, movie_name):
|
||||
|
||||
check_names = [check_name]
|
||||
|
||||
# Match names between "
|
||||
try: check_names.append(re.search(r'([\'"])[^\1]*\1', check_name).group(0))
|
||||
except: pass
|
||||
|
||||
# Match longest name between []
|
||||
try: check_names.append(max(check_name.split('['), key = len))
|
||||
except: pass
|
||||
|
||||
for check_name in list(set(check_names)):
|
||||
check_movie = fireEvent('scanner.name_year', check_name, single = True)
|
||||
|
||||
try:
|
||||
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
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def couldBeReleased(self, is_pre_release, dates, year = None):
|
||||
|
||||
now = int(time.time())
|
||||
@@ -626,7 +488,7 @@ class Searcher(Plugin):
|
||||
|
||||
movie_dict = fireEvent('movie.get', movie_id, single = True)
|
||||
log.info('Trying next release for: %s', getTitle(movie_dict['library']))
|
||||
fireEvent('searcher.single', movie_dict)
|
||||
fireEvent('movie.searcher.single', movie_dict)
|
||||
|
||||
return True
|
||||
|
||||
@@ -636,9 +498,3 @@ class Searcher(Plugin):
|
||||
|
||||
class SearchSetupError(Exception):
|
||||
pass
|
||||
|
||||
class NoDownloaders(SearchSetupError):
|
||||
pass
|
||||
|
||||
class NoProviders(SearchSetupError):
|
||||
pass
|
||||
0
couchpotato/core/media/tv/__init__.py
Normal file
0
couchpotato/core/media/tv/__init__.py
Normal file
6
couchpotato/core/media/tv/_base/__init__.py
Normal file
6
couchpotato/core/media/tv/_base/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .main import TVBase
|
||||
|
||||
def start():
|
||||
return TVBase()
|
||||
|
||||
config = []
|
||||
13
couchpotato/core/media/tv/_base/main.py
Normal file
13
couchpotato/core/media/tv/_base/main.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media import MediaBase
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class TVBase(MediaBase):
|
||||
|
||||
identifier = 'tv'
|
||||
|
||||
def __init__(self):
|
||||
super(TVBase, self).__init__()
|
||||
|
||||
7
couchpotato/core/media/tv/searcher/__init__.py
Normal file
7
couchpotato/core/media/tv/searcher/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .main import TVSearcher
|
||||
import random
|
||||
|
||||
def start():
|
||||
return TVSearcher()
|
||||
|
||||
config = []
|
||||
12
couchpotato/core/media/tv/searcher/main.py
Normal file
12
couchpotato/core/media/tv/searcher/main.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class TVSearcher(Plugin):
|
||||
|
||||
in_progress = False
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
@@ -36,4 +36,4 @@ class Automation(Plugin):
|
||||
|
||||
for movie_id in movie_ids:
|
||||
movie_dict = fireEvent('movie.get', movie_id, single = True)
|
||||
fireEvent('searcher.single', movie_dict)
|
||||
fireEvent('movie.searcher.single', movie_dict)
|
||||
|
||||
@@ -37,7 +37,6 @@ var CategoryListBase = new Class({
|
||||
self.settings.addEvent('create', function(){
|
||||
var renamer_group = self.settings.tabs.renamer.groups.renamer;
|
||||
|
||||
p(renamer_group.getElement('.renamer_to'))
|
||||
self.categories.each(function(category){
|
||||
|
||||
var input = new Option.Directory('section_name', 'option.name', category.get('destination'), {
|
||||
|
||||
@@ -70,9 +70,9 @@ class Dashboard(Plugin):
|
||||
coming_soon = False
|
||||
|
||||
# Theater quality
|
||||
if pp.get('theater') and fireEvent('searcher.could_be_released', True, eta, movie.library.year, single = True):
|
||||
if pp.get('theater') and fireEvent('movie.searcher.could_be_released', True, eta, movie.library.year, single = True):
|
||||
coming_soon = True
|
||||
if pp.get('dvd') and fireEvent('searcher.could_be_released', False, eta, movie.library.year, single = True):
|
||||
if pp.get('dvd') and fireEvent('movie.searcher.could_be_released', False, eta, movie.library.year, single = True):
|
||||
coming_soon = True
|
||||
|
||||
# Skip if movie is snatched/downloaded/available
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
from .main import MoviePlugin
|
||||
|
||||
def start():
|
||||
return MoviePlugin()
|
||||
|
||||
config = []
|
||||
@@ -715,7 +715,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
db.commit()
|
||||
|
||||
if self.conf('next_on_failed'):
|
||||
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
|
||||
fireEvent('movie.searcher.try_next_release', movie_id = rel.movie_id)
|
||||
elif item['status'] == 'completed':
|
||||
log.info('Download of %s completed!', item['name'])
|
||||
if self.statusInfoComplete(item):
|
||||
|
||||
@@ -257,7 +257,7 @@ class ResultList(list):
|
||||
|
||||
new_result = self.fillResult(result)
|
||||
|
||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||
is_correct_movie = fireEvent('movie.searcher.correct_movie',
|
||||
nzb = new_result, movie = self.movie, quality = self.quality,
|
||||
imdb_results = self.kwargs.get('imdb_results', False), single = True)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Page.Wanted = new Class({
|
||||
|
||||
if(!self.search_in_progress){
|
||||
|
||||
Api.request('searcher.full_search');
|
||||
Api.request('movie.searcher.full_search');
|
||||
self.startProgressInterval();
|
||||
|
||||
}
|
||||
@@ -53,7 +53,7 @@ Page.Wanted = new Class({
|
||||
var start_text = self.manual_search.get('text');
|
||||
self.progress_interval = setInterval(function(){
|
||||
if(self.search_progress && self.search_progress.running) return;
|
||||
self.search_progress = Api.request('searcher.progress', {
|
||||
self.search_progress = Api.request('movie.searcher.progress', {
|
||||
'onComplete': function(json){
|
||||
self.search_in_progress = true;
|
||||
if(!json.progress){
|
||||
|
||||
Reference in New Issue
Block a user