Put the renamer as a function of a download status checker in the searcher class

The changed functionality is as follows:
- the renamer is not on scheduled interval anymore
- the download status checker checks the status of all snatched releases every x minutes
- if a release has downloaded it fires up the renamer (if enabled)
- if it failed, it sets the release to ignored and snatches the next best release

With these additions the renamer wont scan your hd anymore when it is not required, and will retry failed downloads with new releases.

To do:
- the only downloader implemented is SabNZBd, for the others it defaults to the old behavior when releases are snatched (I think!?)
- a button to scan manually: Items added to the renamer folder are only picked up after a download completed
This commit is contained in:
mano3m committed 2012-08-02 18:59:04 +02:00
1 parent 100b563e10
commit 4223ed4b5a
7 files changed
+172 -108

No files matched your search

+3 -3
View File
@@ -13,16 +13,16 @@ class Downloader(Plugin):
def __init__(self): def __init__(self):
addEvent('download', self.download) addEvent('download', self.download)
addEvent('getdownloadfailed', self.getdownloadfailed) addEvent('getdownloadstatus', self.getdownloadstatus)
def download(self, data = {}, movie = {}, manual = False, filedata = None): def download(self, data = {}, movie = {}, manual = False, filedata = None):
pass pass
def getdownloadfailed(self, data = {}, movie = {}): def getdownloadstatus(self, data = {}, movie = {}):
pass pass
def createNzbName(self, data, movie): def createNzbName(self, data, movie):
return '%s%s' % (toSafeString(data.get('name')), self.cpTag(movie)) return '%s%s' % (toSafeString(data.get('name')[:40]), self.cpTag(movie))
def createFileName(self, data, filedata, movie): def createFileName(self, data, filedata, movie):
name = os.path.join(self.createNzbName(data, movie)) name = os.path.join(self.createNzbName(data, movie))
@@ -40,13 +40,6 @@ config = [{
'advanced': True, 'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
}, },
{
'name': 'download failed',
'default': True,
'type': 'bool',
'advanced': True,
'description': 'Try the next best release for a movie after a download failed.',
},
{ {
'name': 'delete failed', 'name': 'delete failed',
'default': True, 'default': True,
+76 -33
View File
@@ -40,14 +40,14 @@ class Sabnzbd(Downloader):
try: try:
if params.get('mode') is 'addfile': if params.get('mode') is 'addfile':
data = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False) sab = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False)
else: else:
data = self.urlopen(url, timeout = 60, show_error = False) sab = self.urlopen(url, timeout = 60, show_error = False)
except: except:
log.error(traceback.format_exc()) log.error(traceback.format_exc())
return False return False
result = data.strip() result = sab.strip()
if not result: if not result:
log.error("SABnzbd didn't return anything.") log.error("SABnzbd didn't return anything.")
return False return False
@@ -63,53 +63,96 @@ class Sabnzbd(Downloader):
log.error("Unknown error: " + result[:40]) log.error("Unknown error: " + result[:40])
return False return False
def getdownloadfailed(self, data = {}, movie = {}): def getdownloadstatus(self, data = {}, movie = {}):
if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')):
return return
if not self.conf('download failed', default = True): nzbname = self.createNzbName(data, movie)
return False log.info('Checking download status of "%s" at SABnzbd.', nzbname)
log.info('Checking download status of "%s" at SABnzbd.', data.get('name')) # Go through Queue
params = {
'apikey': self.conf('api_key'),
'mode': 'queue',
'output': 'json'
}
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
try:
sab = self.urlopen(url, timeout = 60, show_error = False)
except:
log.error(traceback.format_exc())
return
try:
history = json.loads(sab)
except:
log.debug("Result text from SAB: " + sab[:40])
log.error(traceback.format_exc())
return
for slot in history['queue']['slots']:
if slot['cat'] == self.conf('category'):
log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft']))
if slot['filename'] == nzbname:
return slot['status']
# Go through history items
params = { params = {
'apikey': self.conf('api_key'), 'apikey': self.conf('api_key'),
'mode': 'history', 'mode': 'history',
'output': 'json' 'output': 'json'
} }
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
log.debug('Opening: %s', url)
try: try:
history = json.load(self.urlopen(url)) sab = self.urlopen(url, timeout = 60, show_error = False)
except: except:
log.error(traceback.format_exc()) log.error(traceback.format_exc())
return False return
try:
history = json.loads(sab)
except:
log.debug("Result text from SAB: " + sab[:40])
log.error(traceback.format_exc())
return
nzbname = self.createNzbName(data, movie)
# Go through history items
for slot in history['history']['slots']: for slot in history['history']['slots']:
log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) if slot['category'] == self.conf('category'):
if slot['name'] == nzbname and slot['status'] == 'Failed': log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status']))
if slot['name'] == nzbname:
if slot['status'] == 'Failed' or 'fail' in slot['fail_message'].lower():
# Delete failed download # Delete failed download
if self.conf('delete failed', default = True): if self.conf('delete failed', default = True):
log.info('%s failed downloading, deleting...', slot['name']) log.info('%s failed downloading, deleting...', slot['name'])
params = { params = {
'apikey': self.conf('api_key'), 'apikey': self.conf('api_key'),
'mode': 'history', 'mode': 'history',
'name': 'delete', 'name': 'delete',
'del_files': '1', 'del_files': '1',
'value': slot['nzo_id'] 'value': slot['nzo_id']
} }
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
try: try:
data = self.urlopen(url, timeout = 60, show_error = False) sab = self.urlopen(url, timeout = 60, show_error = False)
except: except:
log.error(traceback.format_exc()) log.error(traceback.format_exc())
return False
# Return download failed result = sab.strip()
return True if not result:
log.error("SABnzbd didn't return anything.")
return False log.debug("Result text from SAB: " + result[:40])
if result == "ok":
log.info('SabNZBd deleted failed release %s successfully.', slot['name'])
elif result == "Missing authentication":
log.error("Incorrect username/password.")
else:
log.error("Unknown error: " + result[:40])
return 'Failed'
else:
return slot['status']
return 'Not found'
@@ -86,15 +86,6 @@ config = [{
'label': 'Separator', 'label': 'Separator',
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.', 'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
}, },
{
'advanced': True,
'name': 'run_every',
'label': 'Run every',
'default': 1,
'type': 'int',
'unit': 'min(s)',
'description': 'Search for new movies inside the folder every X minutes.',
},
], ],
}, { }, {
'tab': 'renamer', 'tab': 'renamer',
+1 -56
View File
@@ -29,7 +29,7 @@ class Renamer(Plugin):
addEvent('renamer.scan', self.scan) addEvent('renamer.scan', self.scan)
addEvent('app.load', self.scan) addEvent('app.load', self.scan)
fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every')) #fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every'))
def scanView(self): def scanView(self):
@@ -48,8 +48,6 @@ class Renamer(Plugin):
log.info('Renamer is disabled to avoid infinite looping of the same error.') log.info('Renamer is disabled to avoid infinite looping of the same error.')
return return
self.checkSnatchedStatusses()
# Check to see if the "to" folder is inside the "from" folder. # Check to see if the "to" folder is inside the "from" folder.
if not os.path.isdir(self.conf('from')) or not os.path.isdir(self.conf('to')): if not os.path.isdir(self.conf('from')) or not os.path.isdir(self.conf('to')):
log.debug('"To" and "From" have to exist.') log.debug('"To" and "From" have to exist.')
@@ -474,57 +472,4 @@ class Renamer(Plugin):
except: except:
log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
def checkSnatchedStatusses(self):
snatched_status = fireEvent('status.get', 'snatched', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True)
db = get_session()
rels = db.query(Relea).filter_by(status_id = snatched_status.get('id'))
for rel in rels:
# Get current selected title
default_title = ''
for title in rel.movie.library.titles:
if title.default: default_title = title.title
log.debug('Checking snatched movie: %s' , default_title)
item = {}
for info in rel.info:
item[info.identifier] = info.value
log.debug('Checking status snatched release: %s' , item.get('name'))
mov = rel.movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
})
# check status
downloadfailed = fireEvent('getdownloadfailed', data = item, movie = mov)
if downloadfailed:
# if failed set status to ignored
rel.status_id = ignored_status.get('id')
db.commit()
# search/download again
# if downloaded manually: # this is currently not stored...
# log.info('Download of %s failed...', item['name'])
# return
#update movie to reflect release status update
mov = rel.movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
})
log.info('Download of %s failed, trying next release...', item['name'])
fireEvent('searcher.single', mov)
return
@@ -39,6 +39,22 @@ config = [{
'type': 'dropdown', 'type': 'dropdown',
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')], 'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
}, },
{
'advanced': True,
'name': 'run_every',
'label': 'Run every',
'default': 1,
'type': 'int',
'unit': 'min(s)',
'description': 'Detect movie status every X minutes. Will start the renamer if movie is <strong>completed</strong> or handle <strong>failed</strong> download if these options are enabled',
},
{
'name': 'failed download',
'default': True,
'type': 'bool',
'advanced': True,
'description': 'Try the next best release for a movie after a download failed.',
},
], ],
}, { }, {
'tab': 'searcher', 'tab': 'searcher',
+76
View File
@@ -25,9 +25,12 @@ class Searcher(Plugin):
addEvent('searcher.single', self.single) addEvent('searcher.single', self.single)
addEvent('searcher.correct_movie', self.correctMovie) addEvent('searcher.correct_movie', self.correctMovie)
addEvent('searcher.download', self.download) addEvent('searcher.download', self.download)
addEvent('searcher.checksnatched', self.checksnatched)
# Schedule cronjob # Schedule cronjob
fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
fireEvent('schedule.interval', 'searcher.checksnatched', self.checksnatched, minutes = self.conf('run_every'))
def all_movies(self): def all_movies(self):
@@ -439,3 +442,76 @@ class Searcher(Plugin):
return False return False
def checksnatched(self):
snatched_status = fireEvent('status.get', 'snatched', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True)
db = get_session()
rels = db.query(Release).filter_by(status_id = snatched_status.get('id'))
log.info('Checking snatched releases...')
for rel in rels:
# Get current selected title
default_title = ''
for title in rel.movie.library.titles:
if title.default: default_title = title.title
log.debug('Checking snatched movie: %s' , default_title)
item = {}
for info in rel.info:
item[info.identifier] = info.value
movie = rel.movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
})
# check status
downloadstatus = fireEvent('getdownloadstatus', data = item, movie = movie)
log.debug('Download staus: %s' , downloadstatus[0])
if downloadstatus[0] == 'Failed':
# if failed set status to ignored
rel.status_id = ignored_status.get('id')
db.commit()
# search/download again
# if downloaded manually: # this is currently not stored...
# log.info('Download of %s failed...', item['name'])
# return
if self.conf('failed download', default = True):
#update movie to reflect release status update
movie = rel.movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
})
log.info('Download of %s failed, trying next release...', item['name'])
fireEvent('searcher.single', movie)
else:
log.info('Download of %s failed.', item['name'])
elif downloadstatus[0] == 'Completed':
log.info('Download of %s completed!', item['name'])
fireEvent('renamer.scan')
elif downloadstatus[0] == 'Not found':
log.info('%s not found in SabNZBd', item['name'])
rel.status_id = ignored_status.get('id')
db.commit()
elif downloadstatus[0] == None: # Downloader not compatible with download status or
fireEvent('renamer.scan')
# Note that Queued, Downloading, Paused, Repairn and Unpackimg are also available as status
return