Merge branch 'develop4' of https://github.com/mano3m/CouchPotatoServer into mano3m-develop4
This commit is contained in:
@@ -7,19 +7,22 @@ import os
|
|||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Downloader(Plugin):
|
class Downloader(Plugin):
|
||||||
|
|
||||||
type = []
|
type = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('download', self.download)
|
addEvent('download', self.download)
|
||||||
|
addEvent('getdownloadstatus', self.getdownloadstatus)
|
||||||
|
|
||||||
def download(self, data = {}):
|
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
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))
|
||||||
|
|||||||
@@ -35,11 +35,18 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'manual',
|
'name': 'manual',
|
||||||
'default': 0,
|
'default': False,
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
'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': 'delete failed',
|
||||||
|
'default': True,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Delete a release after it\'s download failed.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from couchpotato.core.helpers.encoding import tryUrlencode
|
|||||||
from couchpotato.core.helpers.variable import cleanHost
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
import traceback
|
import traceback
|
||||||
|
import json
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -39,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
|
||||||
@@ -61,3 +62,97 @@ class Sabnzbd(Downloader):
|
|||||||
else:
|
else:
|
||||||
log.error("Unknown error: " + result[:40])
|
log.error("Unknown error: " + result[:40])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def getdownloadstatus(self, data = {}, movie = {}):
|
||||||
|
if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')):
|
||||||
|
return
|
||||||
|
|
||||||
|
nzbname = self.createNzbName(data, movie)
|
||||||
|
log.info('Checking download status of "%s" at SABnzbd.', nzbname)
|
||||||
|
|
||||||
|
# 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 = {
|
||||||
|
'apikey': self.conf('api_key'),
|
||||||
|
'mode': 'history',
|
||||||
|
'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['history']['slots']:
|
||||||
|
if slot['category'] == self.conf('category'):
|
||||||
|
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
|
||||||
|
if self.conf('delete failed', default = True):
|
||||||
|
log.info('%s failed downloading, deleting...', slot['name'])
|
||||||
|
params = {
|
||||||
|
'apikey': self.conf('api_key'),
|
||||||
|
'mode': 'history',
|
||||||
|
'name': 'delete',
|
||||||
|
'del_files': '1',
|
||||||
|
'value': slot['nzo_id']
|
||||||
|
}
|
||||||
|
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 False
|
||||||
|
|
||||||
|
result = sab.strip()
|
||||||
|
if not result:
|
||||||
|
log.error("SABnzbd didn't return anything.")
|
||||||
|
|
||||||
|
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',
|
||||||
|
|||||||
@@ -30,7 +30,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):
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,81 @@ 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 status snatched releases...')
|
||||||
|
scanrequired = False
|
||||||
|
|
||||||
|
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)
|
||||||
|
if not downloadstatus: # Downloader not compatible with download status
|
||||||
|
log.debug('Download status functionality is not implemented for active downloaders.')
|
||||||
|
scanrequired = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
log.debug('Download status: %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'])
|
||||||
|
scanrequired = True
|
||||||
|
|
||||||
|
elif downloadstatus[0] == 'Not found':
|
||||||
|
log.info('%s not found in downloaders', item['name'])
|
||||||
|
rel.status_id = ignored_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd
|
||||||
|
if scanrequired:
|
||||||
|
fireEvent('renamer.scan')
|
||||||
|
|
||||||
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user