nzbToMedia/core/autoProcess/autoProcessTV.py
JoopNL 686761a7e4 Report Downloader failures to SickRage
When extraction is done by SickRage, report failures from Downloader to SickRage.
For example, when download fails in SAB because out of retention SAB reports failed status -1. Or when verification fails SAB reports failed status 1. In such cases also report failure to SickRage. Without this fix, this results in "Returned log from SickBeard was not as expected.!"
2016-02-11 23:18:27 +01:00

351 lines
17 KiB
Python

import copy
import os
import time
import errno
import requests
import json
import core
from core.nzbToMediaAutoFork import autoFork
from core.nzbToMediaSceneExceptions import process_all_exceptions
from core.nzbToMediaUtil import convert_to_ascii, flatten, rmDir, listMediaFiles, remoteDir, import_subs, server_responding, reportNzb
from core import logger
from core.transcoder import transcoder
requests.packages.urllib3.disable_warnings()
class autoProcessTV:
def command_complete(self, url, params, headers, section):
r = None
try:
r = requests.get(url, params=params, headers=headers, stream=True, verify=False, timeout=(30, 60))
except requests.ConnectionError:
logger.error("Unable to open URL: %s" % (url1), section)
return None
if not r.status_code in [requests.codes.ok, requests.codes.created, requests.codes.accepted]:
logger.error("Server returned status %s" % (str(r.status_code)), section)
return None
else:
try:
res = json.loads(r.content)
return res['state']
except:
logger.error("%s did not return expected json data." % section, section)
return None
def CDH(self, url2, headers):
r = None
try:
r = requests.get(url2, params={}, headers=headers, stream=True, verify=False, timeout=(30, 60))
except requests.ConnectionError:
logger.error("Unable to open URL: %s" % (url2), section)
return False
if not r.status_code in [requests.codes.ok, requests.codes.created, requests.codes.accepted]:
logger.error("Server returned status %s" % (str(r.status_code)), section)
return False
else:
try:
res = json.loads(r.content)
return res["enableCompletedDownloadHandling"]
except:
return False
def processEpisode(self, section, dirName, inputName=None, failed=False, clientAgent = "manual", download_id=None, inputCategory=None, failureLink=None):
host = core.CFG[section][inputCategory]["host"]
port = core.CFG[section][inputCategory]["port"]
try:
ssl = int(core.CFG[section][inputCategory]["ssl"])
except:
ssl = 0
if ssl:
protocol = "https://"
else:
protocol = "http://"
try:
web_root = core.CFG[section][inputCategory]["web_root"]
except:
web_root = ""
if not server_responding("%s%s:%s%s" % (protocol,host,port,web_root)):
logger.error("Server did not respond. Exiting", section)
return [1, "%s: Failed to post-process - %s did not respond." % (section, section) ]
# auto-detect correct fork
fork, fork_params = autoFork(section, inputCategory)
try:
username = core.CFG[section][inputCategory]["username"]
password = core.CFG[section][inputCategory]["password"]
except:
username = ""
password = ""
try:
apikey = core.CFG[section][inputCategory]["apikey"]
except:
apikey = ""
try:
delete_failed = int(core.CFG[section][inputCategory]["delete_failed"])
except:
delete_failed = 0
try:
nzbExtractionBy = core.CFG[section][inputCategory]["nzbExtractionBy"]
except:
nzbExtractionBy = "Downloader"
try:
process_method = core.CFG[section][inputCategory]["process_method"]
except:
process_method = None
try:
remote_path = int(core.CFG[section][inputCategory]["remote_path"])
except:
remote_path = 0
try:
wait_for = int(core.CFG[section][inputCategory]["wait_for"])
except:
wait_for = 2
try:
force = int(core.CFG[section][inputCategory]["force"])
except:
force = 0
try:
delete_on = int(core.CFG[section][inputCategory]["delete_on"])
except:
delete_on = 0
try:
extract = int(section[inputCategory]["extract"])
except:
extract = 0
if not os.path.isdir(dirName) and os.path.isfile(dirName): # If the input directory is a file, assume single file download and split dir/name.
dirName = os.path.split(os.path.normpath(dirName))[0]
SpecificPath = os.path.join(dirName, str(inputName))
cleanName = os.path.splitext(SpecificPath)
if cleanName[1] == ".nzb":
SpecificPath = cleanName[0]
if os.path.isdir(SpecificPath):
dirName = SpecificPath
# Attempt to create the directory if it doesn't exist and ignore any
# error stating that it already exists. This fixes a bug where SickRage
# won't process the directory because it doesn't exist.
try:
os.makedirs(dirName) # Attempt to create the directory
except OSError, e:
# Re-raise the error if it wasn't about the directory not existing
if e.errno != errno.EEXIST:
raise
if fork not in core.SICKBEARD_TORRENT or (clientAgent in ['nzbget','sabnzbd'] and nzbExtractionBy != "Destination"):
if inputName:
process_all_exceptions(inputName, dirName)
inputName, dirName = convert_to_ascii(inputName, dirName)
# Now check if tv files exist in destination.
if not listMediaFiles(dirName, media=True, audio=False, meta=False, archives=False):
if listMediaFiles(dirName, media=False, audio=False, meta=False, archives=True) and extract:
logger.debug('Checking for archives to extract in directory: %s' % (dirName))
core.extractFiles(dirName)
inputName, dirName = convert_to_ascii(inputName, dirName)
if listMediaFiles(dirName, media=True, audio=False, meta=False, archives=False): # Check that a video exists. if not, assume failed.
flatten(dirName)
# Check video files for corruption
status = int(failed)
good_files = 0
num_files = 0
for video in listMediaFiles(dirName, media=True, audio=False, meta=False, archives=False):
num_files += 1
if transcoder.isVideoGood(video, status):
good_files += 1
import_subs(video)
if num_files > 0:
if good_files == num_files and not status == 0:
logger.info('Found Valid Videos. Setting status Success')
status = 0
failed = 0
if good_files < num_files and status == 0:
logger.info('Found corrupt videos. Setting status Failed')
status = 1
failed = 1
if os.environ.has_key('NZBOP_VERSION') and os.environ['NZBOP_VERSION'][0:5] >= '14.0':
print('[NZB] MARK=BAD')
if failureLink:
failureLink = failureLink + '&corrupt=true'
elif clientAgent == "manual":
logger.warning("No media files found in directory %s to manually process." % (dirName), section)
return [0, ""] # Success (as far as this script is concerned)
elif nzbExtractionBy == "Destination":
logger.info("Check for media files ignored because nzbExtractionBy is set to Destination.")
if failed == 0:
logger.info("Setting Status Success.")
status = 0
failed = 0
else:
logger.info("Downloader reported an error during download or verification. Processing this as a failed download.")
status = 1
failed = 1
else:
logger.warning("No media files found in directory %s. Processing this as a failed download" % (dirName), section)
status = 1
failed = 1
if os.environ.has_key('NZBOP_VERSION') and os.environ['NZBOP_VERSION'][0:5] >= '14.0':
print('[NZB] MARK=BAD')
if status == 0 and core.TRANSCODE == 1: # only transcode successful downloads
result, newDirName = transcoder.Transcode_directory(dirName)
if result == 0:
logger.debug("SUCCESS: Transcoding succeeded for files in %s" % (dirName), section)
dirName = newDirName
else:
logger.error("FAILED: Transcoding failed for files in %s" % (dirName), section)
return [1, "%s: Failed to post-process - Transcoding failed" % (section) ]
# configure SB params to pass
fork_params['quiet'] = 1
if inputName is not None:
fork_params['nzbName'] = inputName
for param in copy.copy(fork_params):
if param == "failed":
fork_params[param] = failed
if param in ["dirName", "dir", "proc_dir"]:
fork_params[param] = dirName
if remote_path:
fork_params[param] = remoteDir(dirName)
if param == "process_method":
if process_method:
fork_params[param] = process_method
else:
del fork_params[param]
if param == "force":
if force:
fork_params[param] = force
else:
del fork_params[param]
if param == "delete_on":
if delete_on:
fork_params[param] = delete_on
else:
del fork_params[param]
# delete any unused params so we don't pass them to SB by mistake
[fork_params.pop(k) for k,v in fork_params.items() if v is None]
if status == 0:
logger.postprocess("SUCCESS: The download succeeded, sending a post-process request", section)
else:
core.FAILED = True
if failureLink:
reportNzb(failureLink, clientAgent)
if fork in core.SICKBEARD_FAILED:
logger.postprocess("FAILED: The download failed. Sending 'failed' process request to %s branch" % (fork), section)
elif section == "NzbDrone":
logger.postprocess("FAILED: The download failed. Sending failed download to %s for CDH processing" % (fork), section)
return [1, "%s: Downlaod Failed. Sending back to %s" % (section, section) ] # Return as failed to flag this in the downloader.
else:
logger.postprocess("FAILED: The download failed. %s branch does not handle failed downloads. Nothing to process" % (fork), section)
if delete_failed and os.path.isdir(dirName) and not os.path.dirname(dirName) == dirName:
logger.postprocess("Deleting failed files and folder %s" % (dirName), section)
rmDir(dirName)
return [1, "%s: Failed to post-process. %s does not support failed downloads" % (section, section) ] # Return as failed to flag this in the downloader.
url = None
if section == "SickBeard":
url = "%s%s:%s%s/home/postprocess/processEpisode" % (protocol,host,port,web_root)
elif section == "NzbDrone":
url = "%s%s:%s%s/api/command" % (protocol, host, port, web_root)
url2 = "%s%s:%s%s/api/config/downloadClient" % (protocol, host, port, web_root)
headers = {"X-Api-Key": apikey}
params = {'sortKey': 'series.title', 'page': 1, 'pageSize': 1, 'sortDir': 'asc'}
if remote_path:
logger.debug("remote_path: %s" % (remoteDir(dirName)),section)
data = {"name": "DownloadedEpisodesScan", "path": remoteDir(dirName), "downloadClientId": download_id}
else:
logger.debug("path: %s" % (dirName),section)
data = {"name": "DownloadedEpisodesScan", "path": dirName, "downloadClientId": download_id}
if not download_id:
data.pop("downloadClientId")
data = json.dumps(data)
try:
if section == "SickBeard":
logger.debug("Opening URL: %s with params: %s" % (url, str(fork_params)), section)
r = None
s = requests.Session()
login = "%s%s:%s%s/login" % (protocol,host,port,web_root)
login_params = {'username': username, 'password': password}
s.post(login, data=login_params, stream=True, verify=False, timeout=(30, 60))
r = s.get(url, auth=(username, password), params=fork_params, stream=True, verify=False, timeout=(30, 1800))
elif section == "NzbDrone":
logger.debug("Opening URL: %s with data: %s" % (url, str(data)), section)
r = None
r = requests.post(url, data=data, headers=headers, stream=True, verify=False, timeout=(30, 1800))
except requests.ConnectionError:
logger.error("Unable to open URL: %s" % (url), section)
return [1, "%s: Failed to post-process - Unable to connect to %s" % (section, section) ]
if not r.status_code in [requests.codes.ok, requests.codes.created, requests.codes.accepted]:
logger.error("Server returned status %s" % (str(r.status_code)), section)
return [1, "%s: Failed to post-process - Server returned status %s" % (section, str(r.status_code)) ]
Success = False
Started = False
if section == "SickBeard":
for line in r.iter_lines():
if line:
logger.postprocess("%s" % (line), section)
if "Moving file from" in line:
inputName = os.path.split(line)[1]
if "Processing succeeded" in line or "Successfully processed" in line:
Success = True
elif section == "NzbDrone":
try:
res = json.loads(r.content)
scan_id = int(res['id'])
logger.debug("Scan started with id: %s" % (str(scan_id)), section)
Started = True
except Exception as e:
logger.warning("No scan id was returned due to: %s" % (e), section)
scan_id = None
Started = False
if status != 0 and delete_failed and not os.path.dirname(dirName) == dirName:
logger.postprocess("Deleting failed files and folder %s" % (dirName),section)
rmDir(dirName)
if Success:
return [0, "%s: Successfully post-processed %s" % (section, inputName) ]
elif section == "NzbDrone" and Started:
n = 0
params = {}
url = url + "/" + str(scan_id)
while n < 6: # set up wait_for minutes to see if command completes..
time.sleep(10 * wait_for)
command_status = self.command_complete(url, params, headers, section)
if command_status and command_status in ['completed', 'failed']:
break
n += 1
if command_status:
logger.debug("The Scan command return status: %s" % (command_status), section)
if not os.path.exists(dirName):
logger.debug("The directory %s has been removed. Renaming was successful." % (dirName), section)
return [0, "%s: Successfully post-processed %s" % (section, inputName) ]
elif command_status and command_status in ['completed']:
logger.debug("The Scan command has completed successfully. Renaming was successful.", section)
return [0, "%s: Successfully post-processed %s" % (section, inputName) ]
elif command_status and command_status in ['failed']:
logger.debug("The Scan command has failed. Renaming was not successful.", section)
#return [1, "%s: Failed to post-process %s" % (section, inputName) ]
if self.CDH(url2, headers):
logger.debug("The Scan command did not return status completed, but complete Download Handling is enabled. Passing back to %s." % (section), section)
return [status, "%s: Complete DownLoad Handling is enabled. Passing back to %s" % (section, section) ]
else:
logger.warning("The Scan command did not return a valid status. Renaming was not successful.", section)
return [1, "%s: Failed to post-process %s" % (section, inputName) ]
else:
return [1, "%s: Failed to post-process - Returned log from %s was not as expected." % (section, section) ] # We did not receive Success confirmation.