Merge branch 'hotfix/0.13.7'

This commit is contained in:
JackDandy 2017-12-27 03:15:36 +00:00
commit 4a8d3d7543
21 changed files with 586 additions and 144 deletions

View file

@ -1,4 +1,14 @@
### 0.13.6 (2017-12-13 01:50:00 UTC) ### 0.13.7 (2017-12-27 03:00:00 UTC)
* Add log message for not found on indexer when adding a new show
* Fix upgrade once ARCHIVED setting by postProcessor
* Fix determination of is_first_best_match
* Fix BTScene and Lime
* Add ETTV torrent provider
* Add PotUK torrent provider
### 0.13.6 (2017-12-13 01:50:00 UTC)
* Change improve multi episode release search * Change improve multi episode release search
* Change improve usage of the optional regex library * Change improve usage of the optional regex library

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

View file

@ -340,7 +340,7 @@
#if $show.paused #if $show.paused
<span class="label label-paused">Paused</span> <span class="label label-paused">Paused</span>
#end if #end if
#if ($anyQualities + $bestQualities) and int($show.archive_firstmatch) #if ($anyQualities + $bestQualities) and int($show.upgrade_once)
<span class="label">Upgrade once</span> <span class="label">Upgrade once</span>
#end if #end if
#if $show.exceptions #if $show.exceptions

View file

@ -152,10 +152,10 @@
#if $anyQualities + $bestQualities #if $anyQualities + $bestQualities
<div class="field-pair show-if-quality-custom" style="display:none"> <div class="field-pair show-if-quality-custom" style="display:none">
<label for="archive_firstmatch"> <label for="upgrade-once">
<span class="component-title">Upgrade once</span> <span class="component-title">Upgrade once</span>
<span class="component-desc"> <span class="component-desc">
<input type="checkbox" name="archive_firstmatch" id="archive_firstmatch"#echo ('', $html_checked)[$show.archive_firstmatch]#> <input type="checkbox" name="upgrade_once" id="upgrade-once"#echo ('', $html_checked)[$show.upgrade_once]#>
<p>stop upgrading after matching the first best <em>Upgrade to</em> quality</p> <p>stop upgrading after matching the first best <em>Upgrade to</em> quality</p>
</span> </span>
</label> </label>

View file

@ -88,11 +88,11 @@
#set $isSelected = ' selected="selected"' #set $isSelected = ' selected="selected"'
#set $isEnabled = $isSelected #set $isEnabled = $isSelected
#set $isDisabled = $isSelected #set $isDisabled = $isSelected
#if $archive_firstmatch_value##set $isDisabled = ''##else##set $isEnabled = ''##end if# #if $upgrade_once_value##set $isDisabled = ''##else##set $isEnabled = ''##end if#
<div class="optionWrapper clearfix"> <div class="optionWrapper clearfix">
<span class="selectTitle">Upgrade once</span> <span class="selectTitle">Upgrade once</span>
<div class="selectChoices"> <div class="selectChoices">
<select id="edit_archive_firstmatch" name="archive_firstmatch" class="form-control form-control-inline input-sm"> <select id="edit_upgrade-once" name="upgrade_once" class="form-control form-control-inline input-sm">
<option value="keep">&lt; keep &gt;</option> <option value="keep">&lt; keep &gt;</option>
<option value="enable"${isEnabled}>enable</option> <option value="enable"${isEnabled}>enable</option>
<option value="disable"${isDisabled}>disable</option> <option value="disable"${isDisabled}>disable</option>

View file

@ -507,6 +507,8 @@ IGNORE_WORDS = 'core2hd, hevc, MrLss, reenc, x265, danish, deutsch, dutch, flemi
'german, italian, nordic, norwegian, portuguese, spanish, swedish, turkish' 'german, italian, nordic, norwegian, portuguese, spanish, swedish, turkish'
REQUIRE_WORDS = '' REQUIRE_WORDS = ''
WANTEDLIST_CACHE = None
CALENDAR_UNPROTECTED = False CALENDAR_UNPROTECTED = False
TMDB_API_KEY = 'edc5f123313769de83a71e157758030b' TMDB_API_KEY = 'edc5f123313769de83a71e157758030b'
@ -555,7 +557,7 @@ def initialize(console_logging=True):
global __INITIALIZED__, showList, providerList, newznabProviderList, torrentRssProviderList, \ global __INITIALIZED__, showList, providerList, newznabProviderList, torrentRssProviderList, \
WEB_HOST, WEB_ROOT, ACTUAL_CACHE_DIR, CACHE_DIR, ZONEINFO_DIR, ADD_SHOWS_WO_DIR, CREATE_MISSING_SHOW_DIRS, \ WEB_HOST, WEB_ROOT, ACTUAL_CACHE_DIR, CACHE_DIR, ZONEINFO_DIR, ADD_SHOWS_WO_DIR, CREATE_MISSING_SHOW_DIRS, \
RECENTSEARCH_STARTUP, NAMING_FORCE_FOLDERS, SOCKET_TIMEOUT, DEBUG, INDEXER_DEFAULT, CONFIG_FILE, \ RECENTSEARCH_STARTUP, NAMING_FORCE_FOLDERS, SOCKET_TIMEOUT, DEBUG, INDEXER_DEFAULT, CONFIG_FILE, \
REMOVE_FILENAME_CHARS, IMPORT_DEFAULT_CHECKED_SHOWS REMOVE_FILENAME_CHARS, IMPORT_DEFAULT_CHECKED_SHOWS, WANTEDLIST_CACHE
# Schedulers # Schedulers
# global traktCheckerScheduler # global traktCheckerScheduler
global recentSearchScheduler, backlogSearchScheduler, showUpdateScheduler, \ global recentSearchScheduler, backlogSearchScheduler, showUpdateScheduler, \
@ -677,6 +679,8 @@ def initialize(console_logging=True):
update_config = False update_config = False
WANTEDLIST_CACHE = common.wantedQualities()
# wanted branch # wanted branch
BRANCH = check_setting_str(CFG, 'General', 'branch', '') BRANCH = check_setting_str(CFG, 'General', 'branch', '')

View file

@ -377,6 +377,62 @@ class Quality:
FAILED = None FAILED = None
class wantedQualities(dict):
wantedlist = 1
bothlists = 2
upgradelist = 3
def __init__(self, **kwargs):
super(wantedQualities, self).__init__(**kwargs)
def _generate_wantedlist(self, qualities):
initial_qualities, upgrade_qualities = Quality.splitQuality(qualities)
max_initial_quality = max(initial_qualities or [Quality.NONE])
self[qualities] = {0: {self.bothlists: False, self.wantedlist: initial_qualities, self.upgradelist: False}}
for q in Quality.qualityStrings:
if 0 >= q:
continue
if q not in upgrade_qualities and q in initial_qualities:
# quality is only in initial_qualities
self[qualities][q] = {self.bothlists: False,
self.wantedlist: [i for i in upgrade_qualities if q < i], self.upgradelist: False}
elif q in upgrade_qualities and q in initial_qualities:
# quality is in initial_qualities and upgrade_qualities
self[qualities][q] = {self.bothlists: True,
self.wantedlist: [i for i in upgrade_qualities if q < i], self.upgradelist: True}
elif q in upgrade_qualities:
# quality is only in upgrade_qualities
self[qualities][q] = {self.bothlists: False,
self.wantedlist: [i for i in upgrade_qualities if q < i], self.upgradelist: True}
else:
# quality is not in any selected quality for the show
only_upgrade = q >= max_initial_quality
self[qualities][q] = {self.bothlists: False,
self.wantedlist:
[i for i in (initial_qualities, upgrade_qualities)[only_upgrade] if q < i],
self.upgradelist: only_upgrade}
def __getitem__(self, k):
if k not in self:
self._generate_wantedlist(k)
return super(wantedQualities, self).__getitem__(k)
def get(self, k, *args, **kwargs):
if k not in self:
self._generate_wantedlist(k)
return super(wantedQualities, self).get(k, *args, **kwargs)
def get_wantedlist(self, qualities, upgradeonce, quality, status, unaired=False, manual=False):
if not manual and status in [ARCHIVED, IGNORED, SKIPPED] + ([UNAIRED], [])[unaired]:
return []
if upgradeonce:
if status == SNATCHED_BEST or \
(not self[qualities][quality][self.bothlists] and self[qualities][quality][self.upgradelist] and
status in (DOWNLOADED, SNATCHED, SNATCHED_BEST, SNATCHED_PROPER)):
return []
return self[qualities][quality][self.wantedlist]
Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()] Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()]
Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()] Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()]
Quality.SNATCHED_BEST = [Quality.compositeStatus(SNATCHED_BEST, x) for x in Quality.qualityStrings.keys()] Quality.SNATCHED_BEST = [Quality.compositeStatus(SNATCHED_BEST, x) for x in Quality.qualityStrings.keys()]

View file

@ -939,12 +939,17 @@ class PostProcessor(object):
cur_ep.release_name = self.release_name or '' cur_ep.release_name = self.release_name or ''
any_qualities, best_qualities = common.Quality.splitQuality(cur_ep.show.quality) any_qualities, best_qualities = common.Quality.splitQuality(cur_ep.show.quality)
cur_status, cur_quality = common.Quality.splitCompositeStatus(cur_ep.status)
cur_ep.status = common.Quality.compositeStatus( cur_ep.status = common.Quality.compositeStatus(
**({'status': common.DOWNLOADED, 'quality': new_ep_quality}, **({'status': common.DOWNLOADED, 'quality': new_ep_quality},
{'status': common.ARCHIVED, 'quality': new_ep_quality}) {'status': common.ARCHIVED, 'quality': new_ep_quality})
[ep_obj.status in common.Quality.SNATCHED_BEST or [ep_obj.status in common.Quality.SNATCHED_BEST or
(cur_ep.show.archive_firstmatch and new_ep_quality in best_qualities)]) (cur_ep.show.upgrade_once and
(new_ep_quality in best_qualities and
(new_ep_quality not in any_qualities or (cur_status in
(common.SNATCHED, common.SNATCHED_BEST, common.SNATCHED_PROPER, common.DOWNLOADED) and
cur_quality != new_ep_quality))))])
cur_ep.release_group = self.release_group or '' cur_ep.release_group = self.release_group or ''

View file

@ -26,9 +26,9 @@ from sickbeard import logger, encodingKludge as ek
# usenet # usenet
from . import newznab, omgwtfnzbs from . import newznab, omgwtfnzbs
# torrent # torrent
from . import alpharatio, beyondhd, bithdtv, bitmetv, blutopia, btn, btscene, dh, \ from . import alpharatio, beyondhd, bithdtv, bitmetv, blutopia, btn, btscene, dh, ettv, \
fano, filelist, funfile, gftracker, grabtheinfo, hd4free, hdbits, hdspace, hdtorrents, \ fano, filelist, funfile, gftracker, grabtheinfo, hd4free, hdbits, hdspace, hdtorrents, \
iptorrents, limetorrents, magnetdl, morethan, nebulance, ncore, nyaa, pisexy, pretome, privatehd, ptf, \ iptorrents, limetorrents, magnetdl, morethan, nebulance, ncore, nyaa, pisexy, potuk, pretome, privatehd, ptf, \
rarbg, revtt, scenehd, scenetime, shazbat, skytorrents, speedcd, \ rarbg, revtt, scenehd, scenetime, shazbat, skytorrents, speedcd, \
thepiratebay, torlock, torrentbytes, torrentday, torrenting, torrentleech, \ thepiratebay, torlock, torrentbytes, torrentday, torrenting, torrentleech, \
torrentvault, torrentz2, tvchaosuk, wop, zooqle torrentvault, torrentz2, tvchaosuk, wop, zooqle
@ -51,6 +51,7 @@ __all__ = ['omgwtfnzbs',
'btscene', 'btscene',
'custom01', 'custom01',
'dh', 'dh',
'ettv',
'fano', 'fano',
'filelist', 'filelist',
'funfile', 'funfile',
@ -68,6 +69,7 @@ __all__ = ['omgwtfnzbs',
'ncore', 'ncore',
'nyaa', 'nyaa',
'pisexy', 'pisexy',
'potuk',
'pretome', 'pretome',
'privatehd', 'privatehd',
'ptf', 'ptf',

View file

@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>. # along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import base64
import re import re
import traceback import traceback
import urllib import urllib
@ -31,8 +32,13 @@ class BTSceneProvider(generic.TorrentProvider):
def __init__(self): def __init__(self):
generic.TorrentProvider.__init__(self, 'BTScene') generic.TorrentProvider.__init__(self, 'BTScene')
self.url_home = ['http://btsone.cc/', 'http://diriri.xyz/', 'http://mytorrentz.tv/'] self.url_home = ['http://btsone.cc/', 'http://diriri.xyz/'] + \
['https://%s/' % base64.b64decode(x) for x in [''.join(x) for x in [
[re.sub('[L\sT]+', '', x[::-1]) for x in [
'zTRnTY', 'uVT 2Y', '15LSTZ', 's JmLb', 'rTNL2b', 'uQW LZ', '=LLMmd']],
[re.sub('[j\sq]+', '', x[::-1]) for x in [
'zRn qY', 'l52j b', '1j5S M', 'sq Jmb', 'r Nq2b', 'ujQWqZ', 's9jGqb']],
]]]
self.url_vars = {'search': '?q=%s&category=series&order=1', 'browse': 'lastdaycat/type/Series/', self.url_vars = {'search': '?q=%s&category=series&order=1', 'browse': 'lastdaycat/type/Series/',
'get': 'torrentdownload.php?id=%s'} 'get': 'torrentdownload.php?id=%s'}
self.url_tmpl = {'config_provider_home_uri': '%(home)s', 'search': '%(vars)s', self.url_tmpl = {'config_provider_home_uri': '%(home)s', 'search': '%(vars)s',
@ -58,6 +64,9 @@ class BTSceneProvider(generic.TorrentProvider):
url = self.url url = self.url
response = self.get_url(url) response = self.get_url(url)
if not response:
return results
form = re.findall('(?is)(<form[^>]+)', response) form = re.findall('(?is)(<form[^>]+)', response)
response = any(form) and form[0] or response response = any(form) and form[0] or response
action = re.findall('<form[^>]+action=[\'"]([^\'"]*)', response)[0] action = re.findall('<form[^>]+action=[\'"]([^\'"]*)', response)[0]

129
sickbeard/providers/ettv.py Normal file
View file

@ -0,0 +1,129 @@
# coding=utf-8
#
# This file is part of SickGear.
#
# SickGear is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SickGear is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import re
import traceback
from . import generic
from sickbeard import logger
from sickbeard.bs4_parser import BS4Parser
from sickbeard.helpers import tryInt
from lib.unidecode import unidecode
class ETTVProvider(generic.TorrentProvider):
def __init__(self):
generic.TorrentProvider.__init__(self, 'ETTV')
self.url_home = ['https://ettv.tv/']
self.url_vars = {'search': 'torrents-search.php?%s&search=%s&sort=id&order=desc'}
self.url_tmpl = {'config_provider_home_uri': '%(home)s', 'search': '%(home)s%(vars)s'}
self.categories = {'Season': [7], 'Episode': [41, 5, 50]}
self.categories['Cache'] = self.categories['Season'] + self.categories['Episode']
self.minseed, self.minleech = 2 * [None]
@staticmethod
def _has_signature(data=None):
return data and re.search(r'(?i)(?:ettv)', data)
def _search_provider(self, search_params, **kwargs):
results = []
if not self.url:
return results
items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []}
rc = dict((k, re.compile('(?i)' + v)) for (k, v) in {'info': 'torrent/'}.iteritems())
for mode in search_params.keys():
for search_string in search_params[mode]:
search_string = isinstance(search_string, unicode) and unidecode(search_string) or search_string
search_url = self.urls['search'] % (
self._categories_string(mode), ('%2B ', '')['Cache' == mode] + '.'.join(search_string.split()))
html = self.get_url(search_url)
cnt = len(items[mode])
try:
if not html or self._has_no_results(html):
raise generic.HaltParseException
with BS4Parser(html, features=['html5lib', 'permissive']) as soup:
torrent_table = soup.find('table', class_='table')
torrent_rows = [] if not torrent_table else torrent_table.find_all('tr')
if not len(torrent_rows):
raise generic.HaltParseException
head = None
for tr in torrent_rows[1:]:
cells = tr.find_all('td')
if 6 > len(cells):
continue
try:
head = head if None is not head else self._header_row(
tr, {'seed': r'seed', 'leech': r'leech', 'size': r'^size'})
seeders, leechers, size = [tryInt(n, n) for n in [
cells[head[x]].get_text().strip() for x in 'seed', 'leech', 'size']]
if self._peers_fail(mode, seeders, leechers):
continue
info = tr.find('a', href=rc['info'])
title = (info.attrs.get('title') or info.get_text()).strip()
download_url = self._link(info.get('href'))
except (AttributeError, TypeError, ValueError, IndexError):
continue
if title and download_url:
items[mode].append((title, download_url, seeders, self._bytesizer(size)))
except generic.HaltParseException:
pass
except (StandardError, Exception):
logger.log(u'Failed to parse. Traceback: %s' % traceback.format_exc(), logger.ERROR)
self._log_search(mode, len(items[mode]) - cnt, search_url)
results = self._sort_seeding(mode, results + items[mode])
return results
def get_data(self, url):
result = None
html = self.get_url(url, timeout=90)
try:
result = re.findall('(?i)"(magnet:[^"]+?)">', html)[0]
except IndexError:
logger.log('Failed no magnet in response', logger.DEBUG)
return result
def get_result(self, episodes, url):
result = None
if url:
result = super(ETTVProvider, self).get_result(episodes, url)
result.get_data_func = self.get_data
return result
provider = ETTVProvider()

View file

@ -34,18 +34,8 @@ class LimeTorrentsProvider(generic.TorrentProvider):
self.url_home = ['https://www.limetorrents.cc/'] + \ self.url_home = ['https://www.limetorrents.cc/'] + \
['https://%s/' % base64.b64decode(x) for x in [''.join(x) for x in [ ['https://%s/' % base64.b64decode(x) for x in [''.join(x) for x in [
[re.sub('[f\sX]+', '', x[::-1]) for x in [ [re.sub('[ \sF]+', '', x[::-1]) for x in [
'tlXGfb', '1X5SfZ', 'sfJfmb', 'rN 2Xb', 'u QfWZ', 's9G b']], 'X ZtlFGb', 'lJnc vR', 'n LzR nb', 'vxmYuF V', 'CFZltF2Y', '==wYF2F5']],
[re.sub('[ \ss]+', '', x[::-1]) for x in [
'Ztl Gsb', 'nc svRX', 'Rs nblJ', '5 JmLz', 'czsFsGc', 'nLskVs2', '0s N']],
[re.sub('[1\sF]+', '', x[::-1]) for x in [
'X Zt1lGb', 'l1Jn1cvR', 'mL11zRnb', 'uVXbtFFl', 'Hdp NWFa', '=1FQ3cuk']],
[re.sub('[y\sW]+', '', x[::-1]) for x in [
'XWZtlyGb', 'lJnyWcvR', 'nyLzRn b', 'vxmYWuWV', 'CWZlt2yY', '== Adyz5']],
[re.sub('[j\sy]+', '', x[::-1]) for x in [
'XyZtlG b', 'lJjnjcvR', 'njLz Rnb', 'vjxmYyuV', 'Gbyhjt2Y', 'n jJ3buw']],
[re.sub('[o\sg]+', '', x[::-1]) for x in [
'XZt lgGb', 'loJn cvR', 'ngLz Rnb', 'v xgmYuV', 'Gbh t2gY', '6l Heu w']],
]]] ]]]
self.url_vars = {'search': 'search/tv/%s/', 'browse': 'browse-torrents/TV-shows/'} self.url_vars = {'search': 'search/tv/%s/', 'browse': 'browse-torrents/TV-shows/'}

View file

@ -35,6 +35,7 @@ from io import BytesIO
from lib.dateutil import parser from lib.dateutil import parser
from sickbeard.network_timezones import sb_timezone from sickbeard.network_timezones import sb_timezone
from sickbeard.helpers import tryInt from sickbeard.helpers import tryInt
from sickbeard.search import get_wanted_qualities, get_aired_in_season
try: try:
from lxml import etree from lxml import etree
@ -473,18 +474,30 @@ class NewznabProvider(generic.NZBProvider):
return show_obj return show_obj
def choose_search_mode(self, episodes, ep_obj, hits_per_page=100): def choose_search_mode(self, episodes, ep_obj, hits_per_page=100):
if not hasattr(ep_obj, 'eps_aired_in_season'):
return None, neededQualities(need_all_qualities=True), hits_per_page
searches = [e for e in episodes if (not ep_obj.show.is_scene and e.season == ep_obj.season) or searches = [e for e in episodes if (not ep_obj.show.is_scene and e.season == ep_obj.season) or
(ep_obj.show.is_scene and e.scene_season == ep_obj.scene_season)] (ep_obj.show.is_scene and e.scene_season == ep_obj.scene_season)]
needed = neededQualities() needed = neededQualities()
needed.check_needed_types(ep_obj.show)
for s in searches: for s in searches:
if needed.all_qualities_needed: if needed.all_qualities_needed:
break break
if not s.show.is_anime and not s.show.is_sports: if not s.show.is_anime and not s.show.is_sports:
if not getattr(s, 'wantedQuality', None):
# this should not happen, the creation is missing for the search in this case
logger.log('wantedQuality property was missing for search, creating it', logger.WARNING)
ep_status, ep_quality = Quality.splitCompositeStatus(ep_obj.status)
s.wantedQuality = get_wanted_qualities(ep_obj, ep_status, ep_quality, unaired=True)
needed.check_needed_qualities(s.wantedQuality) needed.check_needed_qualities(s.wantedQuality)
if not hasattr(ep_obj, 'eps_aired_in_season'):
# this should not happen, the creation is missing for the search in this case
logger.log('eps_aired_in_season property was missing for search, creating it', logger.WARNING)
ep_count, ep_count_scene = get_aired_in_season(ep_obj.show)
ep_obj.eps_aired_in_season = ep_count.get(ep_obj.season, 0)
ep_obj.eps_aired_in_scene_season = ep_count_scene.get(ep_obj.scene_season, 0) if ep_obj.show.is_scene else \
ep_obj.eps_aired_in_season
per_ep, limit_per_ep = 0, 0 per_ep, limit_per_ep = 0, 0
if needed.need_sd and not needed.need_hd: if needed.need_sd and not needed.need_hd:
per_ep, limit_per_ep = 10, 25 per_ep, limit_per_ep = 10, 25
@ -500,15 +513,26 @@ class NewznabProvider(generic.NZBProvider):
rel_per_ep, limit_per_ep = 5, 10 rel_per_ep, limit_per_ep = 5, 10
else: else:
rel_per_ep = per_ep rel_per_ep = per_ep
rel = int(ceil((ep_obj.eps_aired_in_scene_season if ep_obj.show.is_scene else rel = max(1, int(ceil((ep_obj.eps_aired_in_scene_season if ep_obj.show.is_scene else
ep_obj.eps_aired_in_season * rel_per_ep) / hits_per_page)) ep_obj.eps_aired_in_season * rel_per_ep) / hits_per_page)))
rel_limit = int(ceil((ep_obj.eps_aired_in_scene_season if ep_obj.show.is_scene else rel_limit = max(1, int(ceil((ep_obj.eps_aired_in_scene_season if ep_obj.show.is_scene else
ep_obj.eps_aired_in_season * limit_per_ep) / hits_per_page)) ep_obj.eps_aired_in_season * limit_per_ep) / hits_per_page)))
season_search = rel < (len(searches) * 100 // hits_per_page) season_search = rel < (len(searches) * 100 // hits_per_page)
if not season_search: if not season_search:
needed = neededQualities() needed = neededQualities()
needed.check_needed_types(ep_obj.show)
if not ep_obj.show.is_anime and not ep_obj.show.is_sports: if not ep_obj.show.is_anime and not ep_obj.show.is_sports:
if not getattr(ep_obj, 'wantedQuality', None):
ep_status, ep_quality = Quality.splitCompositeStatus(ep_obj.status)
ep_obj.wantedQuality = get_wanted_qualities(ep_obj, ep_status, ep_quality, unaired=True)
needed.check_needed_qualities(ep_obj.wantedQuality) needed.check_needed_qualities(ep_obj.wantedQuality)
else:
if not ep_obj.show.is_anime and not ep_obj.show.is_sports:
for ep in episodes:
if not getattr(ep, 'wantedQuality', None):
ep_status, ep_quality = Quality.splitCompositeStatus(ep.status)
ep.wantedQuality = get_wanted_qualities(ep, ep_status, ep_quality, unaired=True)
needed.check_needed_qualities(ep.wantedQuality)
return (season_search, needed, return (season_search, needed,
(hits_per_page * 100 // hits_per_page * 2, hits_per_page * int(ceil(rel_limit * 1.5)))[season_search]) (hits_per_page * 100 // hits_per_page * 2, hits_per_page * int(ceil(rel_limit * 1.5)))[season_search])

View file

@ -0,0 +1,157 @@
# coding=utf-8
#
# This file is part of SickGear.
#
# SickGear is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SickGear is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import re
import traceback
from . import generic
from sickbeard import logger
from sickbeard.bs4_parser import BS4Parser
from sickbeard.helpers import tryInt
from lib.unidecode import unidecode
class PotUKProvider(generic.TorrentProvider):
def __init__(self):
generic.TorrentProvider.__init__(self, 'PotUK')
self.url_base = 'http://www.potuk.com/newforum/'
self.urls = {'config_provider_home_uri': self.url_base,
'login': self.url_base + 'search.php',
'browse': self.url_base + 'search.php?do=getdaily&exclude=%s',
'get_data': self.url_base + 'misc.php?do=showattachments&t=%s'}
self.url = self.urls['config_provider_home_uri']
self.digest, self.resp = 2 * [None]
def logged_in(self, resp):
try:
self.resp = re.findall('(?sim)<form .*?search.php.*?</form>', resp)[0]
except (IndexError, TypeError):
return False
return self.has_all_cookies('bbsessionhash')
def _authorised(self, **kwargs):
return super(PotUKProvider, self)._authorised(
logged_in=(lambda y=None: self.logged_in(y)),
failed_msg=(lambda y=None: u'Invalid cookie details for %s. Check settings'))
def _search_provider(self, search_params, **kwargs):
results = []
if not self._authorised():
return results
items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []}
opts = re.findall('(?sim)forumchoice\[\][^<]+(.*?)</select>', self.resp)[0]
cat_opts = re.findall(r'(?mis)<option[^>]*?value=[\'"](\d+)[^>]*>(.*?)</option>', opts)
include = []
tv = False
for c in cat_opts:
if not tv and 'TV Shows' in c[1]:
tv = True
elif tv:
if 3 > len(re.findall('&nbsp;', c[1])):
break
elif not filter(lambda v: v in c[1], ('Requests', 'Offer', 'Discussion')):
include += [c[0]]
exclude = ','.join(list(filter(lambda v: v not in include, map(lambda x: x[0], cat_opts))))
for mode in search_params.keys():
for search_string in search_params[mode]:
search_string = isinstance(search_string, unicode) and unidecode(search_string) or search_string
params = {}
if 'Cache' == mode:
search_url = self.urls['browse'] % exclude
else:
search_url = self._link(re.findall('(?i)action="([^"]+?)"', self.resp)[0])
params = {'query': search_string, 'showposts': 0, 'titleonly': 1, 'prefixchoice': '',
'replyless': 0, 'searchdate': 0, 'beforeafter': 'after', 'sortby': 'threadstart',
'order': 'descending', 'starteronly': 0, 'forumchoice': include}
tags = re.findall(r'(?is)(<input[^>]*?name=[\'"][^\'"]+[^>]*)', self.resp)
attrs = [[(re.findall(r'(?is)%s=[\'"]([^\'"]+)' % attr, c) or [''])[0]
for attr in ['type', 'name', 'value']] for c in tags]
for itype, name, value in attrs:
params.setdefault(name, value)
del params['doprefs']
html = self.get_url(search_url, post_data=params)
cnt = len(items[mode])
try:
if not html or self._has_no_results(html):
raise generic.HaltParseException
with BS4Parser(html, features=['html5lib', 'permissive']) as soup:
torrent_table = soup.find('table', id='threadslist')
torrent_rows = [] if not torrent_table else torrent_table.find_all('tr')
if 2 > len(torrent_rows):
raise generic.HaltParseException
for tr in torrent_rows[1:]:
if 6 > len(tr.find_all('td')) or not tr.select('img[alt*="ttach"]'):
continue
try:
link = tr.select('td[id^="td_threadtitle"]')[0].select('a[id*="title"]')[0]
title = link.get_text().strip()
download_url = self.urls['get_data'] % re.findall('t=(\d+)', link['href'])[0]
except (AttributeError, TypeError, ValueError, IndexError):
continue
if title and download_url:
items[mode].append((title, download_url, '', ''))
except generic.HaltParseException:
pass
except (StandardError, Exception):
logger.log(u'Failed to parse. Traceback: %s' % traceback.format_exc(), logger.ERROR)
self._log_search(
mode, len(items[mode]) - cnt, ('search_param: ' + search_string, search_url)['Cache' == mode])
results = self._sort_seeding(mode, results + items[mode])
return results
def get_data(self, url):
result = None
html = self.get_url(url, timeout=90)
try:
result = self._link(re.findall('(?i)"(attachment\.php[^"]+?)"', html)[0])
except IndexError:
logger.log('Failed no torrent in response', logger.DEBUG)
return result
def get_result(self, episodes, url):
result = None
if url:
result = super(PotUKProvider, self).get_result(episodes, url)
result.get_data_func = self.get_data
return result
def ui_string(self, key):
return ('%s_digest' % self.get_id()) == key and 'use... \'bbuserid=xx; bbpassword=yy\'' or ''
provider = PotUKProvider()

View file

@ -26,7 +26,7 @@ import traceback
import sickbeard import sickbeard
from common import SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, Quality, SEASON_RESULT, MULTI_EP_RESULT from common import SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, DOWNLOADED, Quality, SEASON_RESULT, MULTI_EP_RESULT
from sickbeard import logger, db, show_name_helpers, exceptions, helpers from sickbeard import logger, db, show_name_helpers, exceptions, helpers
from sickbeard import sab from sickbeard import sab
@ -41,6 +41,7 @@ from sickbeard import failed_history
from sickbeard.exceptions import ex from sickbeard.exceptions import ex
from sickbeard.providers.generic import GenericProvider from sickbeard.providers.generic import GenericProvider
from sickbeard import common from sickbeard import common
from sickbeard.tv import TVEpisode
def _download_result(result): def _download_result(result):
@ -130,6 +131,11 @@ def snatch_episode(result, end_status=SNATCHED):
# TORRENTs can be sent to clients or saved to disk # TORRENTs can be sent to clients or saved to disk
elif 'torrent' == result.resultType: elif 'torrent' == result.resultType:
if not result.url.startswith('magnet') and None is not result.get_data_func:
result.url = result.get_data_func(result.url)
result.get_data_func = None # consume only once
if not result.url:
return False
# torrents are saved to disk when blackhole mode # torrents are saved to disk when blackhole mode
if 'blackhole' == sickbeard.TORRENT_METHOD: if 'blackhole' == sickbeard.TORRENT_METHOD:
dl_result = _download_result(result) dl_result = _download_result(result)
@ -165,7 +171,7 @@ def snatch_episode(result, end_status=SNATCHED):
update_imdb_data = True update_imdb_data = True
for cur_ep_obj in result.episodes: for cur_ep_obj in result.episodes:
with cur_ep_obj.lock: with cur_ep_obj.lock:
if is_first_best_match(result): if is_first_best_match(cur_ep_obj.status, result):
cur_ep_obj.status = Quality.compositeStatus(SNATCHED_BEST, result.quality) cur_ep_obj.status = Quality.compositeStatus(SNATCHED_BEST, result.quality)
else: else:
cur_ep_obj.status = Quality.compositeStatus(end_status, result.quality) cur_ep_obj.status = Quality.compositeStatus(end_status, result.quality)
@ -289,7 +295,7 @@ def is_final_result(result):
return False return False
def is_first_best_match(result): def is_first_best_match(ep_status, result):
""" """
Checks if the given result is a best quality match and if we want to archive the episode on first match. Checks if the given result is a best quality match and if we want to archive the episode on first match.
""" """
@ -298,21 +304,41 @@ def is_first_best_match(result):
result.name, logger.DEBUG) result.name, logger.DEBUG)
show_obj = result.episodes[0].show show_obj = result.episodes[0].show
cur_status, cur_quality = Quality.splitCompositeStatus(ep_status)
any_qualities, best_qualities = Quality.splitQuality(show_obj.quality) any_qualities, best_qualities = Quality.splitQuality(show_obj.quality)
# if there is a redownload that's a match to one of our best qualities and # if there is a redownload that's a match to one of our best qualities and
# we want to archive the episode then we are done # we want to archive the episode then we are done
if best_qualities and show_obj.archive_firstmatch and result.quality in best_qualities: if best_qualities and show_obj.upgrade_once and \
(result.quality in best_qualities and
(cur_status in (SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, DOWNLOADED) or
result.quality not in any_qualities)):
return True return True
return False return False
def wanted_episodes(show, from_date, make_dict=False, unaired=False): def set_wanted_aired(ep_obj, unaired, ep_count, ep_count_scene, manual=False):
initial_qualities, upgrade_qualities = common.Quality.splitQuality(show.quality) ep_status, ep_quality = common.Quality.splitCompositeStatus(ep_obj.status)
all_qualities = list(set(initial_qualities + upgrade_qualities)) ep_obj.wantedQuality = get_wanted_qualities(ep_obj, ep_status, ep_quality, unaired=unaired, manual=manual)
ep_obj.eps_aired_in_season = ep_count.get(ep_obj.season, 0)
ep_obj.eps_aired_in_scene_season = ep_count_scene.get(
ep_obj.scene_season, 0) if ep_obj.scene_season else ep_obj.eps_aired_in_season
def get_wanted_qualities(ep_obj, cur_status, cur_quality, unaired=False, manual=False):
if isinstance(ep_obj, TVEpisode):
return sickbeard.WANTEDLIST_CACHE.get_wantedlist(ep_obj.show.quality, ep_obj.show.upgrade_once,
cur_quality, cur_status, unaired, manual)
return []
def get_aired_in_season(show, return_sql=False):
ep_count = {}
ep_count_scene = {}
tomorrow = (datetime.date.today() + datetime.timedelta(days=1)).toordinal()
my_db = db.DBConnection() my_db = db.DBConnection()
if show.air_by_date: if show.air_by_date:
@ -326,9 +352,6 @@ def wanted_episodes(show, from_date, make_dict=False, unaired=False):
'WHERE showid = ? AND indexer = ? AND season > 0' 'WHERE showid = ? AND indexer = ? AND season > 0'
sql_results = my_db.select(sql_string, [show.indexerid, show.indexer]) sql_results = my_db.select(sql_string, [show.indexerid, show.indexer])
ep_count = {}
ep_count_scene = {}
tomorrow = (datetime.date.today() + datetime.timedelta(days=1)).toordinal()
for result in sql_results: for result in sql_results:
if 1 < helpers.tryInt(result['airdate']) <= tomorrow: if 1 < helpers.tryInt(result['airdate']) <= tomorrow:
cur_season = helpers.tryInt(result['season']) cur_season = helpers.tryInt(result['season'])
@ -337,76 +360,57 @@ def wanted_episodes(show, from_date, make_dict=False, unaired=False):
if -1 != cur_scene_season: if -1 != cur_scene_season:
ep_count_scene[cur_scene_season] = ep_count.setdefault(cur_scene_season, 0) + 1 ep_count_scene[cur_scene_season] = ep_count.setdefault(cur_scene_season, 0) + 1
if return_sql:
return ep_count, ep_count_scene, sql_results
return ep_count, ep_count_scene
def wanted_episodes(show, from_date, make_dict=False, unaired=False):
ep_count, ep_count_scene, sql_results_org = get_aired_in_season(show, return_sql=True)
from_date_ord = from_date.toordinal()
if unaired: if unaired:
status_list = [common.WANTED, common.FAILED, common.UNAIRED] sql_results = [s for s in sql_results_org if s['airdate'] > from_date_ord or s['airdate'] == 1]
sql_string += ' AND ( airdate > ? OR airdate = 1 )'
else: else:
status_list = [common.WANTED, common.FAILED] sql_results = [s for s in sql_results_org if s['airdate'] > from_date_ord]
sql_string += ' AND airdate > ?'
sql_results = my_db.select(sql_string, [show.indexerid, show.indexer, from_date.toordinal()])
# check through the list of statuses to see if we want any
if make_dict: if make_dict:
wanted = {} wanted = {}
else: else:
wanted = [] wanted = []
total_wanted = total_replacing = total_unaired = 0
downloaded_status_list = common.SNATCHED_ANY + [common.DOWNLOADED]
for result in sql_results:
not_downloaded = True
cur_composite_status = int(result['status'])
cur_status, cur_quality = common.Quality.splitCompositeStatus(cur_composite_status)
cur_snatched = cur_status in downloaded_status_list
if show.archive_firstmatch and cur_snatched and cur_quality in upgrade_qualities: total_wanted = total_replacing = total_unaired = 0
if 0 < len(sql_results) and 2 < len(sql_results) - len(show.episodes):
myDB = db.DBConnection()
show_ep_sql = myDB.select('SELECT * FROM tv_episodes WHERE showid = ? AND indexer = ?',
[show.indexerid, show.indexer])
else:
show_ep_sql = None
for result in sql_results:
ep_obj = show.getEpisode(int(result['season']), int(result['episode']), ep_sql=show_ep_sql)
cur_status, cur_quality = common.Quality.splitCompositeStatus(ep_obj.status)
ep_obj.wantedQuality = get_wanted_qualities(ep_obj, cur_status, cur_quality, unaired=unaired)
if not ep_obj.wantedQuality:
continue continue
# special case: already downloaded quality is not in any of the upgrade to Qualities ep_obj.eps_aired_in_season = ep_count.get(helpers.tryInt(result['season']), 0)
other_quality_downloaded = False ep_obj.eps_aired_in_scene_season = ep_count_scene.get(
if len(upgrade_qualities) and cur_snatched and cur_quality not in all_qualities: helpers.tryInt(result['scene_season']), 0) if result['scene_season'] else ep_obj.eps_aired_in_season
other_quality_downloaded = True if make_dict:
wanted_qualities = all_qualities wanted.setdefault(ep_obj.scene_season if ep_obj.show.is_scene else ep_obj.season, []).append(ep_obj)
else: else:
wanted_qualities = upgrade_qualities wanted.append(ep_obj)
if upgrade_qualities: if cur_status in (common.WANTED, common.FAILED):
highest_wanted_quality = max(wanted_qualities) total_wanted += 1
elif cur_status in (common.UNAIRED, common.SKIPPED, common.IGNORED, common.UNKNOWN):
total_unaired += 1
else: else:
if other_quality_downloaded: total_replacing += 1
highest_wanted_quality = max(initial_qualities)
else:
highest_wanted_quality = 0
# if we need a better one then say yes
if (cur_snatched and cur_quality < highest_wanted_quality) \
or cur_status in status_list \
or (sickbeard.SEARCH_UNAIRED and 1 == result['airdate']
and cur_status in (common.SKIPPED, common.IGNORED, common.UNAIRED, common.UNKNOWN, common.FAILED)):
if cur_status in (common.WANTED, common.FAILED):
total_wanted += 1
elif cur_status in (common.UNAIRED, common.SKIPPED, common.IGNORED, common.UNKNOWN):
total_unaired += 1
else:
total_replacing += 1
not_downloaded = False
ep_obj = show.getEpisode(int(result['season']), int(result['episode']))
ep_obj.wantedQuality = [i for i in (wanted_qualities, initial_qualities)[not_downloaded]
if cur_quality < i]
# in case we don't want any quality for this episode, skip the episode
if 0 == len(ep_obj.wantedQuality):
logger.log('Dropped episode, no wanted quality for %sx%s: [%s]' % (
ep_obj.season, ep_obj.episode, ep_obj.show.name), logger.ERROR)
continue
ep_obj.eps_aired_in_season = ep_count.get(helpers.tryInt(result['season']), 0)
ep_obj.eps_aired_in_scene_season = ep_count_scene.get(
helpers.tryInt(result['scene_season']), 0) if result['scene_season'] else ep_obj.eps_aired_in_season
if make_dict:
wanted.setdefault(ep_obj.scene_season if ep_obj.show.is_scene else ep_obj.season, []).append(ep_obj)
else:
wanted.append(ep_obj)
if 0 < total_wanted + total_replacing + total_unaired: if 0 < total_wanted + total_replacing + total_unaired:
actions = [] actions = []
@ -739,6 +743,11 @@ def search_providers(show, episodes, manual_search=False, torrent_only=False, tr
# filter out possible bad torrents from providers # filter out possible bad torrents from providers
if 'torrent' == best_result.resultType: if 'torrent' == best_result.resultType:
if not best_result.url.startswith('magnet') and None is not best_result.get_data_func:
best_result.url = best_result.get_data_func(best_result.url)
best_result.get_data_func = None # consume only once
if not best_result.url:
continue
if best_result.url.startswith('magnet'): if best_result.url.startswith('magnet'):
if 'blackhole' != sickbeard.TORRENT_METHOD: if 'blackhole' != sickbeard.TORRENT_METHOD:
best_result.content = None best_result.content = None

View file

@ -25,8 +25,7 @@ import datetime
import sickbeard import sickbeard
from sickbeard import db, logger, common, exceptions, helpers, network_timezones, generic_queue, search, \ from sickbeard import db, logger, common, exceptions, helpers, network_timezones, generic_queue, search, \
failed_history, history, ui, properFinder failed_history, history, ui, properFinder
from sickbeard.search import wanted_episodes from sickbeard.search import wanted_episodes, get_aired_in_season, set_wanted_aired
from sickbeard.common import Quality
search_queue_lock = threading.Lock() search_queue_lock = threading.Lock()
@ -369,6 +368,9 @@ class ManualSearchQueueItem(generic_queue.QueueItem):
logger.log(u'Beginning manual search for: [%s]' % self.segment.prettyName()) logger.log(u'Beginning manual search for: [%s]' % self.segment.prettyName())
self.started = True self.started = True
ep_count, ep_count_scene = get_aired_in_season(self.show)
set_wanted_aired(self.segment, True, ep_count, ep_count_scene, manual=True)
search_result = search.search_providers(self.show, [self.segment], True, try_other_searches=True) search_result = search.search_providers(self.show, [self.segment], True, try_other_searches=True)
if search_result: if search_result:
@ -415,6 +417,11 @@ class BacklogQueueItem(generic_queue.QueueItem):
is_error = False is_error = False
try: try:
if not self.standard_backlog:
ep_count, ep_count_scene = get_aired_in_season(self.show)
for ep_obj in self.segment:
set_wanted_aired(ep_obj, True, ep_count, ep_count_scene)
logger.log(u'Beginning backlog search for: [%s]' % self.show.name) logger.log(u'Beginning backlog search for: [%s]' % self.show.name)
search_result = search.search_providers( search_result = search.search_providers(
self.show, self.segment, False, self.show, self.segment, False,
@ -454,12 +461,11 @@ class FailedQueueItem(generic_queue.QueueItem):
self.started = True self.started = True
try: try:
ep_count, ep_count_scene = get_aired_in_season(self.show)
for ep_obj in self.segment: for ep_obj in self.segment:
logger.log(u'Marking episode as bad: [%s]' % ep_obj.prettyName()) logger.log(u'Marking episode as bad: [%s]' % ep_obj.prettyName())
cur_status = ep_obj.status
failed_history.set_episode_failed(ep_obj) failed_history.set_episode_failed(ep_obj)
(release, provider) = failed_history.find_release(ep_obj) (release, provider) = failed_history.find_release(ep_obj)
failed_history.revert_episode(ep_obj) failed_history.revert_episode(ep_obj)
@ -469,8 +475,9 @@ class FailedQueueItem(generic_queue.QueueItem):
logger.log(u'Beginning failed download search for: [%s]' % ep_obj.prettyName()) logger.log(u'Beginning failed download search for: [%s]' % ep_obj.prettyName())
search_result = search.search_providers( set_wanted_aired(ep_obj, True, ep_count, ep_count_scene)
self.show, self.segment, True, try_other_searches=True, old_status=cur_status)
search_result = search.search_providers(self.show, self.segment, True, try_other_searches=True)
if search_result: if search_result:
for result in search_result: for result in search_result:

View file

@ -177,10 +177,10 @@ class ShowQueue(generic_queue.GenericQueue):
def addShow(self, indexer, indexer_id, showDir, default_status=None, quality=None, flatten_folders=None, def addShow(self, indexer, indexer_id, showDir, default_status=None, quality=None, flatten_folders=None,
lang='en', subtitles=None, anime=None, scene=None, paused=None, blacklist=None, whitelist=None, lang='en', subtitles=None, anime=None, scene=None, paused=None, blacklist=None, whitelist=None,
wanted_begin=None, wanted_latest=None, tag=None, new_show=False): wanted_begin=None, wanted_latest=None, tag=None, new_show=False, show_name=None):
queueItemObj = QueueItemAdd(indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, queueItemObj = QueueItemAdd(indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang,
subtitles, anime, scene, paused, blacklist, whitelist, subtitles, anime, scene, paused, blacklist, whitelist,
wanted_begin, wanted_latest, tag, new_show=new_show) wanted_begin, wanted_latest, tag, new_show=new_show, show_name=show_name)
self.add_item(queueItemObj) self.add_item(queueItemObj)
@ -238,7 +238,7 @@ class ShowQueueItem(generic_queue.QueueItem):
class QueueItemAdd(ShowQueueItem): class QueueItemAdd(ShowQueueItem):
def __init__(self, indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, subtitles, anime, def __init__(self, indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, subtitles, anime,
scene, paused, blacklist, whitelist, default_wanted_begin, default_wanted_latest, tag, scene, paused, blacklist, whitelist, default_wanted_begin, default_wanted_latest, tag,
scheduled_update=False, new_show=False): scheduled_update=False, new_show=False, show_name=None):
self.indexer = indexer self.indexer = indexer
self.indexer_id = indexer_id self.indexer_id = indexer_id
@ -257,6 +257,7 @@ class QueueItemAdd(ShowQueueItem):
self.whitelist = whitelist self.whitelist = whitelist
self.tag = tag self.tag = tag
self.new_show = new_show self.new_show = new_show
self.showname = show_name
self.show = None self.show = None
@ -270,7 +271,9 @@ class QueueItemAdd(ShowQueueItem):
Returns the show name if there is a show object created, if not returns Returns the show name if there is a show object created, if not returns
the dir that the show is being added to. the dir that the show is being added to.
""" """
if self.show == None: if None is not self.showname:
return self.showname
if None is self.show:
return self.showDir return self.showDir
return self.show.name return self.show.name
@ -304,6 +307,12 @@ class QueueItemAdd(ShowQueueItem):
t = sickbeard.indexerApi(self.indexer).indexer(**lINDEXER_API_PARMS) t = sickbeard.indexerApi(self.indexer).indexer(**lINDEXER_API_PARMS)
s = t[self.indexer_id, False] s = t[self.indexer_id, False]
if getattr(t, 'show_not_found', False):
logger.log('Show %s was not found on %s, maybe show was deleted' %
(self.show_name, sickbeard.indexerApi(self.indexer).name), logger.ERROR)
self._finishEarly()
return
# this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that has no proper english version of the show # this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that has no proper english version of the show
if getattr(s, 'seriesname', None) is None: if getattr(s, 'seriesname', None) is None:
logger.log('Show in %s has no name on %s, probably the wrong language used to search with.' % logger.log('Show in %s has no name on %s, probably the wrong language used to search with.' %

View file

@ -109,7 +109,7 @@ class TVShow(object):
self._air_by_date = 0 self._air_by_date = 0
self._subtitles = int(sickbeard.SUBTITLES_DEFAULT if sickbeard.SUBTITLES_DEFAULT else 0) self._subtitles = int(sickbeard.SUBTITLES_DEFAULT if sickbeard.SUBTITLES_DEFAULT else 0)
self._dvdorder = 0 self._dvdorder = 0
self._archive_firstmatch = 0 self._upgrade_once = 0
self._lang = lang self._lang = lang
self._last_update_indexer = 1 self._last_update_indexer = 1
self._sports = 0 self._sports = 0
@ -156,7 +156,7 @@ class TVShow(object):
air_by_date = property(lambda self: self._air_by_date, dirty_setter('_air_by_date')) air_by_date = property(lambda self: self._air_by_date, dirty_setter('_air_by_date'))
subtitles = property(lambda self: self._subtitles, dirty_setter('_subtitles')) subtitles = property(lambda self: self._subtitles, dirty_setter('_subtitles'))
dvdorder = property(lambda self: self._dvdorder, dirty_setter('_dvdorder')) dvdorder = property(lambda self: self._dvdorder, dirty_setter('_dvdorder'))
archive_firstmatch = property(lambda self: self._archive_firstmatch, dirty_setter('_archive_firstmatch')) upgrade_once = property(lambda self: self._upgrade_once, dirty_setter('_upgrade_once'))
lang = property(lambda self: self._lang, dirty_setter('_lang')) lang = property(lambda self: self._lang, dirty_setter('_lang'))
last_update_indexer = property(lambda self: self._last_update_indexer, dirty_setter('_last_update_indexer')) last_update_indexer = property(lambda self: self._last_update_indexer, dirty_setter('_last_update_indexer'))
sports = property(lambda self: self._sports, dirty_setter('_sports')) sports = property(lambda self: self._sports, dirty_setter('_sports'))
@ -358,7 +358,8 @@ class TVShow(object):
return ep_list return ep_list
def getEpisode(self, season=None, episode=None, file=None, noCreate=False, absolute_number=None, forceUpdate=False): def getEpisode(self, season=None, episode=None, file=None, noCreate=False, absolute_number=None, forceUpdate=False,
ep_sql=None):
# if we get an anime get the real season and episode # if we get an anime get the real season and episode
if self.is_anime and absolute_number and not season and not episode: if self.is_anime and absolute_number and not season and not episode:
@ -392,9 +393,9 @@ class TVShow(object):
(self.indexerid, season, episode), logger.DEBUG) (self.indexerid, season, episode), logger.DEBUG)
if file: if file:
ep = TVEpisode(self, season, episode, file) ep = TVEpisode(self, season, episode, file, show_sql=ep_sql)
else: else:
ep = TVEpisode(self, season, episode) ep = TVEpisode(self, season, episode, show_sql=ep_sql)
if ep != None: if ep != None:
self.episodes[season][episode] = ep self.episodes[season][episode] = ep
@ -932,9 +933,9 @@ class TVShow(object):
if not self.dvdorder: if not self.dvdorder:
self.dvdorder = 0 self.dvdorder = 0
self.archive_firstmatch = sqlResults[0]['archive_firstmatch'] self.upgrade_once = sqlResults[0]['archive_firstmatch']
if not self.archive_firstmatch: if not self.upgrade_once:
self.archive_firstmatch = 0 self.upgrade_once = 0
self.quality = int(sqlResults[0]['quality']) self.quality = int(sqlResults[0]['quality'])
self.flatten_folders = int(sqlResults[0]['flatten_folders']) self.flatten_folders = int(sqlResults[0]['flatten_folders'])
@ -1407,7 +1408,7 @@ class TVShow(object):
'sports': self.sports, 'sports': self.sports,
'subtitles': self.subtitles, 'subtitles': self.subtitles,
'dvdorder': self.dvdorder, 'dvdorder': self.dvdorder,
'archive_firstmatch': self.archive_firstmatch, 'archive_firstmatch': self.upgrade_once,
'startyear': self.startyear, 'startyear': self.startyear,
'lang': self.lang, 'lang': self.lang,
'imdb_id': self.imdbid, 'imdb_id': self.imdbid,
@ -1451,6 +1452,30 @@ class TVShow(object):
logger.log('Checking if found %sepisode %sx%s is wanted at quality %s' % logger.log('Checking if found %sepisode %sx%s is wanted at quality %s' %
(('', 'multi-part ')[multi_ep], season, episode, Quality.qualityStrings[quality]), logger.DEBUG) (('', 'multi-part ')[multi_ep], season, episode, Quality.qualityStrings[quality]), logger.DEBUG)
if not multi_ep:
try:
wq = getattr(self.episodes.get(season, {}).get(episode, {}), 'wantedQuality', None)
if None is not wq:
if quality in wq:
curStatus, curQuality = Quality.splitCompositeStatus(self.episodes[season][episode].status)
if curStatus in (WANTED, UNAIRED, SKIPPED, FAILED):
logger.log('Existing episode status is wanted/unaired/skipped/failed, getting found episode',
logger.DEBUG)
return True
elif manualSearch:
logger.log('Usually ignoring found episode, but forced search allows the quality, getting found'
' episode', logger.DEBUG)
return True
elif quality > curQuality:
logger.log(
'Episode already exists but the found episode has better quality, getting found episode',
logger.DEBUG)
return True
logger.log('None of the conditions were met, ignoring found episode', logger.DEBUG)
return False
except (StandardError, Exception):
pass
# if the quality isn't one we want under any circumstances then just say no # if the quality isn't one we want under any circumstances then just say no
initialQualities, archiveQualities = Quality.splitQuality(self.quality) initialQualities, archiveQualities = Quality.splitQuality(self.quality)
allQualities = list(set(initialQualities + archiveQualities)) allQualities = list(set(initialQualities + archiveQualities))
@ -1542,7 +1567,7 @@ class TVShow(object):
min_best, max_best = min(best_qualities), max(best_qualities) min_best, max_best = min(best_qualities), max(best_qualities)
if quality >= max_best \ if quality >= max_best \
or (self.archive_firstmatch and or (self.upgrade_once and
(quality in best_qualities or (None is not min_best and quality > min_best))): (quality in best_qualities or (None is not min_best and quality > min_best))):
return Overview.GOOD return Overview.GOOD
return Overview.QUAL return Overview.QUAL
@ -1558,7 +1583,7 @@ class TVShow(object):
class TVEpisode(object): class TVEpisode(object):
def __init__(self, show, season, episode, file=''): def __init__(self, show, season, episode, file='', show_sql=None):
self._name = '' self._name = ''
self._season = season self._season = season
self._episode = episode self._episode = episode
@ -1593,7 +1618,7 @@ class TVEpisode(object):
self.lock = threading.Lock() self.lock = threading.Lock()
self.specifyEpisode(self.season, self.episode) self.specifyEpisode(self.season, self.episode, show_sql)
self.relatedEps = [] self.relatedEps = []
@ -1738,9 +1763,9 @@ class TVEpisode(object):
# if either setting has changed return true, if not return false # if either setting has changed return true, if not return false
return oldhasnfo != self.hasnfo or oldhastbn != self.hastbn return oldhasnfo != self.hasnfo or oldhastbn != self.hastbn
def specifyEpisode(self, season, episode): def specifyEpisode(self, season, episode, show_sql=None):
sqlResult = self.loadFromDB(season, episode) sqlResult = self.loadFromDB(season, episode, show_sql)
if not sqlResult: if not sqlResult:
# only load from NFO if we didn't load from DB # only load from NFO if we didn't load from DB
@ -1764,13 +1789,17 @@ class TVEpisode(object):
raise exceptions.EpisodeNotFoundException( raise exceptions.EpisodeNotFoundException(
'Couldn\'t find episode %sx%s' % (season, episode)) 'Couldn\'t find episode %sx%s' % (season, episode))
def loadFromDB(self, season, episode): def loadFromDB(self, season, episode, show_sql=None):
logger.log('%s: Loading episode details from DB for episode %sx%s' % (self.show.indexerid, season, episode), logger.log('%s: Loading episode details from DB for episode %sx%s' % (self.show.indexerid, season, episode),
logger.DEBUG) logger.DEBUG)
myDB = db.DBConnection() sql_results = None
sql_results = myDB.select('SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?', if show_sql:
[self.show.indexerid, season, episode]) sql_results = [s for s in show_sql if episode == s['episode'] and season == s['season']]
if not sql_results:
myDB = db.DBConnection()
sql_results = myDB.select('SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?',
[self.show.indexerid, season, episode])
if len(sql_results) > 1: if len(sql_results) > 1:
raise exceptions.MultipleDBEpisodesException('Your DB has two records for the same show somehow.') raise exceptions.MultipleDBEpisodesException('Your DB has two records for the same show somehow.')

View file

@ -1678,7 +1678,7 @@ class Home(MainHandler):
def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[], def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[],
flatten_folders=None, paused=None, directCall=False, air_by_date=None, sports=None, dvdorder=None, flatten_folders=None, paused=None, directCall=False, air_by_date=None, sports=None, dvdorder=None,
indexerLang=None, subtitles=None, archive_firstmatch=None, rls_ignore_words=None, indexerLang=None, subtitles=None, upgrade_once=None, rls_ignore_words=None,
rls_require_words=None, anime=None, blacklist=None, whitelist=None, rls_require_words=None, anime=None, blacklist=None, whitelist=None,
scene=None, tag=None, quality_preset=None, reset_fanart=None, **kwargs): scene=None, tag=None, quality_preset=None, reset_fanart=None, **kwargs):
@ -1757,7 +1757,7 @@ class Home(MainHandler):
flatten_folders = config.checkbox_to_value(flatten_folders) flatten_folders = config.checkbox_to_value(flatten_folders)
dvdorder = config.checkbox_to_value(dvdorder) dvdorder = config.checkbox_to_value(dvdorder)
archive_firstmatch = config.checkbox_to_value(archive_firstmatch) upgrade_once = config.checkbox_to_value(upgrade_once)
paused = config.checkbox_to_value(paused) paused = config.checkbox_to_value(paused)
air_by_date = config.checkbox_to_value(air_by_date) air_by_date = config.checkbox_to_value(air_by_date)
scene = config.checkbox_to_value(scene) scene = config.checkbox_to_value(scene)
@ -1820,7 +1820,7 @@ class Home(MainHandler):
with showObj.lock: with showObj.lock:
newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities)) newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
showObj.quality = newQuality showObj.quality = newQuality
showObj.archive_firstmatch = archive_firstmatch showObj.upgrade_once = upgrade_once
# reversed for now # reversed for now
if bool(showObj.flatten_folders) != bool(flatten_folders): if bool(showObj.flatten_folders) != bool(flatten_folders):
@ -3717,7 +3717,8 @@ class NewHomeAddShows(Home):
sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), newQuality, sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), newQuality,
flatten_folders, indexerLang, subtitles, anime, flatten_folders, indexerLang, subtitles, anime,
scene, None, blacklist, whitelist, scene, None, blacklist, whitelist,
wanted_begin, wanted_latest, tag, new_show=new_show) # @UndefinedVariable wanted_begin, wanted_latest, tag, new_show=new_show,
show_name=show_name) # @UndefinedVariable
# ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir) # ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir)
return finishAddShow() return finishAddShow()
@ -3788,7 +3789,8 @@ class NewHomeAddShows(Home):
flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT, flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT,
subtitles=sickbeard.SUBTITLES_DEFAULT, subtitles=sickbeard.SUBTITLES_DEFAULT,
anime=sickbeard.ANIME_DEFAULT, anime=sickbeard.ANIME_DEFAULT,
scene=sickbeard.SCENE_DEFAULT) scene=sickbeard.SCENE_DEFAULT,
show_name=show_name)
num_added += 1 num_added += 1
if num_added: if num_added:
@ -4131,8 +4133,8 @@ class Manage(MainHandler):
if showObj: if showObj:
showList.append(showObj) showList.append(showObj)
archive_firstmatch_all_same = True upgrade_once_all_same = True
last_archive_firstmatch = None last_upgrade_once = None
flatten_folders_all_same = True flatten_folders_all_same = True
last_flatten_folders = None last_flatten_folders = None
@ -4169,12 +4171,12 @@ class Manage(MainHandler):
if cur_root_dir not in root_dir_list: if cur_root_dir not in root_dir_list:
root_dir_list.append(cur_root_dir) root_dir_list.append(cur_root_dir)
if archive_firstmatch_all_same: if upgrade_once_all_same:
# if we had a value already and this value is different then they're not all the same # if we had a value already and this value is different then they're not all the same
if last_archive_firstmatch not in (None, curShow.archive_firstmatch): if last_upgrade_once not in (None, curShow.upgrade_once):
archive_firstmatch_all_same = False upgrade_once_all_same = False
else: else:
last_archive_firstmatch = curShow.archive_firstmatch last_upgrade_once = curShow.upgrade_once
# if we know they're not all the same then no point even bothering # if we know they're not all the same then no point even bothering
if paused_all_same: if paused_all_same:
@ -4235,7 +4237,7 @@ class Manage(MainHandler):
last_air_by_date = curShow.air_by_date last_air_by_date = curShow.air_by_date
t.showList = toEdit t.showList = toEdit
t.archive_firstmatch_value = last_archive_firstmatch if archive_firstmatch_all_same else None t.upgrade_once_value = last_upgrade_once if upgrade_once_all_same else None
t.paused_value = last_paused if paused_all_same else None t.paused_value = last_paused if paused_all_same else None
t.tag_value = last_tag if tag_all_same else None t.tag_value = last_tag if tag_all_same else None
t.anime_value = last_anime if anime_all_same else None t.anime_value = last_anime if anime_all_same else None
@ -4249,7 +4251,7 @@ class Manage(MainHandler):
return t.respond() return t.respond()
def massEditSubmit(self, archive_firstmatch=None, paused=None, anime=None, sports=None, scene=None, def massEditSubmit(self, upgrade_once=None, paused=None, anime=None, sports=None, scene=None,
flatten_folders=None, quality_preset=False, subtitles=None, air_by_date=None, anyQualities=[], flatten_folders=None, quality_preset=False, subtitles=None, air_by_date=None, anyQualities=[],
bestQualities=[], toEdit=None, tag=None, *args, **kwargs): bestQualities=[], toEdit=None, tag=None, *args, **kwargs):
@ -4285,11 +4287,11 @@ class Manage(MainHandler):
else: else:
new_show_dir = showObj._location new_show_dir = showObj._location
if archive_firstmatch == 'keep': if upgrade_once == 'keep':
new_archive_firstmatch = showObj.archive_firstmatch new_upgrade_once = showObj.upgrade_once
else: else:
new_archive_firstmatch = True if archive_firstmatch == 'enable' else False new_upgrade_once = True if 'enable' == upgrade_once else False
new_archive_firstmatch = 'on' if new_archive_firstmatch else 'off' new_upgrade_once = 'on' if new_upgrade_once else 'off'
if paused == 'keep': if paused == 'keep':
new_paused = showObj.paused new_paused = showObj.paused
@ -4348,7 +4350,7 @@ class Manage(MainHandler):
curErrors += Home(self.application, self.request).editShow(curShow, new_show_dir, anyQualities, curErrors += Home(self.application, self.request).editShow(curShow, new_show_dir, anyQualities,
bestQualities, exceptions_list, bestQualities, exceptions_list,
archive_firstmatch=new_archive_firstmatch, upgrade_once=new_upgrade_once,
flatten_folders=new_flatten_folders, flatten_folders=new_flatten_folders,
paused=new_paused, sports=new_sports, paused=new_paused, sports=new_sports,
subtitles=new_subtitles, anime=new_anime, subtitles=new_subtitles, anime=new_anime,

View file

@ -110,7 +110,7 @@ mainDB.sickbeard.save_config = _dummy_saveConfig
# the real one tries to contact tvdb just stop it from getting more info on the ep # the real one tries to contact tvdb just stop it from getting more info on the ep
def _fake_specifyEP(self, season, episode): def _fake_specifyEP(self, season, episode, show_sql=None):
pass pass
sickbeard.tv.TVEpisode.specifyEpisode = _fake_specifyEP sickbeard.tv.TVEpisode.specifyEpisode = _fake_specifyEP