Fix for failed download issues.

Fix for auto-update improperly restarting.
This commit is contained in:
echel0n 2014-05-13 04:16:32 -07:00
parent 2318e43e89
commit b9310444e5
7 changed files with 153 additions and 144 deletions

View file

@ -96,9 +96,10 @@ def daemonize():
try:
pid = os.fork() # @UndefinedVariable - only available in UNIX
if pid != 0:
sys.exit(0)
os._exit(0)
except OSError, e:
raise RuntimeError("1st fork failed: %s [%d]" % (e.strerror, e.errno))
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
os.setsid() # @UndefinedVariable - only available in UNIX
@ -110,18 +111,33 @@ def daemonize():
try:
pid = os.fork() # @UndefinedVariable - only available in UNIX
if pid != 0:
sys.exit(0)
os._exit(0)
except OSError, e:
raise RuntimeError("2nd fork failed: %s [%d]" % (e.strerror, e.errno))
dev_null = file('/dev/null', 'r')
os.dup2(dev_null.fileno(), sys.stdin.fileno())
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# Write pid
if sickbeard.CREATEPID:
pid = str(os.getpid())
logger.log(u"Writing PID " + pid + " to " + str(sickbeard.PIDFILE))
file(sickbeard.PIDFILE, 'w').write("%s\n" % pid)
logger.log(u"Writing PID: " + pid + " to " + str(sickbeard.PIDFILE))
try:
file(sickbeard.PIDFILE, 'w').write("%s\n" % pid)
except IOError, e:
logger.log_error_and_exit(
u"Unable to write PID file: " + sickbeard.PIDFILE + " Error: " + str(e.strerror) + " [" + str(
e.errno) + "]")
# Redirect all output
sys.stdout.flush()
sys.stderr.flush()
devnull = getattr(os, 'devnull', '/dev/null')
stdin = file(devnull, 'r')
stdout = file(devnull, 'a+')
stderr = file(devnull, 'a+')
os.dup2(stdin.fileno(), sys.stdin.fileno())
os.dup2(stdout.fileno(), sys.stdout.fileno())
os.dup2(stderr.fileno(), sys.stderr.fileno())
def main():
"""
@ -134,8 +150,8 @@ def main():
sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME)
sickbeard.DATA_DIR = sickbeard.PROG_DIR
sickbeard.MY_ARGS = sys.argv[1:]
sickbeard.CREATEPID = False
sickbeard.DAEMON = False
sickbeard.CREATEPID = False
sickbeard.SYS_ENCODING = None
@ -196,13 +212,15 @@ def main():
if o in ('-p', '--port'):
forcedPort = int(a)
# Run as a daemon
# Run as a double forked daemon
if o in ('-d', '--daemon'):
sickbeard.DAEMON = True
# When running as daemon disable consoleLogging and don't start browser
consoleLogging = False
noLaunch = True
if sys.platform == 'win32':
print "Daemonize not supported under Windows, starting normally"
else:
consoleLogging = False
sickbeard.DAEMON = True
sickbeard.DAEMON = False
# Specify folder to load the config file from
if o in ('--config',):
@ -214,21 +232,27 @@ def main():
# Write a pidfile if requested
if o in ('--pidfile',):
sickbeard.CREATEPID = True
sickbeard.PIDFILE = str(a)
# If the pidfile already exists, sickbeard may still be running, so exit
if os.path.exists(sickbeard.PIDFILE):
sys.exit("PID file '" + sickbeard.PIDFILE + "' already exists. Exiting.")
sys.exit("PID file: " + sickbeard.PIDFILE + " already exists. Exiting.")
# The pidfile is only useful in daemon mode, make sure we can write the file properly
if sickbeard.DAEMON:
sickbeard.CREATEPID = True
try:
file(sickbeard.PIDFILE, 'w').write("pid\n")
except IOError, e:
raise SystemExit("Unable to write PID file: %s [%d]" % (e.strerror, e.errno))
else:
logger.log(u"Not running in daemon mode. PID file creation disabled.")
# The pidfile is only useful in daemon mode, make sure we can write the file properly
if sickbeard.CREATEPID:
if sickbeard.DAEMON:
pid_dir = os.path.dirname(sickbeard.PIDFILE)
if not os.access(pid_dir, os.F_OK):
sys.exit("PID dir: " + pid_dir + " doesn't exist. Exiting.")
if not os.access(pid_dir, os.W_OK):
sys.exit("PID dir: " + pid_dir + " must be writable (write permissions). Exiting.")
else:
if consoleLogging:
sys.stdout.write("Not running in daemon mode. PID file creation disabled.\n")
sickbeard.CREATEPID = False
# If they don't specify a config file then put it in the data dir
if not sickbeard.CONFIG_FILE:
@ -263,16 +287,16 @@ def main():
sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
CUR_DB_VERSION = db.DBConnection().checkDBVersion()
CUR_DB_VERSION = db.DBConnection().checkDBVersion()
if CUR_DB_VERSION > 0:
if CUR_DB_VERSION < MIN_DB_VERSION:
raise SystemExit("Your database version (" + str(db.DBConnection().checkDBVersion()) + ") is too old to migrate from with this version of Sick Beard (" + str(MIN_DB_VERSION) + ").\n" + \
"Upgrade using a previous version of SB first, or start with no database file to begin fresh.")
if CUR_DB_VERSION > MAX_DB_VERSION:
raise SystemExit("Your database version (" + str(db.DBConnection().checkDBVersion()) + ") has been incremented past what this version of Sick Beard supports (" + str(MAX_DB_VERSION) + ").\n" + \
"If you have used other forks of SB, your database may be unusable due to their modifications.")
# Initialize the config and our threads
"If you have used other forks of SB, your database may be unusable due to their modifications.")
# Initialize the config and our threads
sickbeard.initialize(consoleLogging=consoleLogging)
sickbeard.showList = []
@ -306,17 +330,17 @@ def main():
try:
initWebServer({
'port': startPort,
'host': webhost,
'data_root': os.path.join(sickbeard.PROG_DIR, 'gui/'+sickbeard.GUI_NAME),
'web_root': sickbeard.WEB_ROOT,
'log_dir': log_dir,
'username': sickbeard.WEB_USERNAME,
'password': sickbeard.WEB_PASSWORD,
'enable_https': sickbeard.ENABLE_HTTPS,
'https_cert': sickbeard.HTTPS_CERT,
'https_key': sickbeard.HTTPS_KEY,
})
'port': startPort,
'host': webhost,
'data_root': os.path.join(sickbeard.PROG_DIR, 'gui/'+sickbeard.GUI_NAME),
'web_root': sickbeard.WEB_ROOT,
'log_dir': log_dir,
'username': sickbeard.WEB_USERNAME,
'password': sickbeard.WEB_PASSWORD,
'enable_https': sickbeard.ENABLE_HTTPS,
'https_cert': sickbeard.HTTPS_CERT,
'https_key': sickbeard.HTTPS_KEY,
})
except IOError:
logger.log(u"Unable to start web server, is something else running on port %d?" % startPort, logger.ERROR)
if sickbeard.LAUNCH_BROWSER and not sickbeard.DAEMON:
@ -341,12 +365,13 @@ def main():
# Stay alive while my threads do the work
while (True):
time.sleep(1)
if sickbeard.invoked_command:
sickbeard.invoked_command()
sickbeard.invoked_command = None
time.sleep(1)
return
if __name__ == "__main__":

View file

@ -1259,12 +1259,21 @@ def halt():
__INITIALIZED__ = False
def remove_pid_file(PIDFILE):
try:
if os.path.exists(PIDFILE):
os.remove(PIDFILE)
except (IOError, OSError):
return False
return True
def sig_handler(signum=None, frame=None):
if type(signum) != type(None):
logger.log(u"Signal %i caught, saving and exiting..." % int(signum))
saveAndShutdown()
def saveAll():
global showList
@ -1288,7 +1297,7 @@ def saveAndShutdown(restart=False):
if CREATEPID:
logger.log(u"Removing pidfile " + str(PIDFILE))
os.remove(PIDFILE)
remove_pid_file(PIDFILE)
if restart:
install_type = versionCheckScheduler.action.install_type
@ -1310,7 +1319,7 @@ def saveAndShutdown(restart=False):
popen_list += MY_ARGS
if '--nolaunch' not in popen_list:
popen_list += ['--nolaunch']
logger.log(u"Restarting Sick Beard with " + str(popen_list))
logger.log(u"Restarting SickRage with " + str(popen_list))
logger.close()
subprocess.Popen(popen_list, cwd=os.getcwd())

View file

@ -22,17 +22,12 @@ import datetime
from sickbeard import db
from sickbeard import logger
from sickbeard import exceptions
from sickbeard.exceptions import ex, EpisodeNotFoundException
from sickbeard.history import dateFormat
from sickbeard.common import Quality
from sickbeard.common import WANTED, FAILED
def _log_helper(message, level=logger.MESSAGE):
logger.log(message, level)
return message + u"\n"
def prepareFailedName(release):
"""Standardizes release name for failed DB"""
@ -41,6 +36,10 @@ def prepareFailedName(release):
fixed = fixed.rpartition(".")[0]
fixed = re.sub("[\.\-\+\ ]", "_", fixed)
if not isinstance(fixed, unicode):
fixed = unicode(fixed, 'utf-8')
return fixed
@ -55,26 +54,26 @@ def logFailed(release):
sql_results = myDB.select("SELECT * FROM history WHERE release=?", [release])
if len(sql_results) == 0:
log_str += _log_helper(
logger.log(
u"Release not found in snatch history. Recording it as bad with no size and no proivder.", logger.WARNING)
log_str += _log_helper(
logger.log(
u"Future releases of the same name from providers that don't return size will be skipped.", logger.WARNING)
elif len(sql_results) > 1:
log_str += _log_helper(u"Multiple logged snatches found for release", logger.WARNING)
logger.log(u"Multiple logged snatches found for release", logger.WARNING)
sizes = len(set(x["size"] for x in sql_results))
providers = len(set(x["provider"] for x in sql_results))
if sizes == 1:
log_str += _log_helper(u"However, they're all the same size. Continuing with found size.", logger.WARNING)
logger.log(u"However, they're all the same size. Continuing with found size.", logger.WARNING)
size = sql_results[0]["size"]
else:
log_str += _log_helper(
logger.log(
u"They also vary in size. Deleting the logged snatches and recording this release with no size/provider",
logger.WARNING)
for result in sql_results:
deleteLoggedSnatch(result["release"], result["size"], result["provider"])
if providers == 1:
log_str += _log_helper(u"They're also from the same provider. Using it as well.")
logger.log(u"They're also from the same provider. Using it as well.")
provider = sql_results[0]["provider"]
else:
size = sql_results[0]["size"]
@ -113,74 +112,45 @@ def hasFailed(release, size, provider="%"):
return (len(sql_results) > 0)
def revertEpisode(show_obj, season, episode=None):
def revertEpisode(epObj):
"""Restore the episodes of a failed download to their original state"""
myDB = db.DBConnection("failed.db")
log_str = u""
sql_results = myDB.select("SELECT * FROM history WHERE showid=? AND season=?", [show_obj.indexerid, season])
# {episode: result, ...}
sql_results = myDB.select("SELECT * FROM history WHERE showid=? AND season=?", [epObj.show.indexerid, epObj.season])
history_eps = dict([(res["episode"], res) for res in sql_results])
if episode:
try:
ep_obj = show_obj.getEpisode(season, episode)
log_str += _log_helper(u"Reverting episode (%s, %s): %s" % (season, episode, ep_obj.name))
with ep_obj.lock:
if episode in history_eps:
log_str += _log_helper(u"Found in history")
ep_obj.status = history_eps[episode]['old_status']
else:
log_str += _log_helper(u"WARNING: Episode not found in history. Setting it back to WANTED",
logger.WARNING)
ep_obj.status = WANTED
try:
logger.log(u"Reverting episode (%s, %s): %s" % (epObj.season, epObj.episode, epObj.name))
with epObj.lock:
if epObj.episode in history_eps:
logger.log(u"Found in history")
epObj.status = history_eps[epObj.episode]['old_status']
else:
logger.log(u"WARNING: Episode not found in history. Setting it back to WANTED",
logger.WARNING)
epObj.status = WANTED
ep_obj.saveToDB()
epObj.saveToDB()
except exceptions.EpisodeNotFoundException, e:
log_str += _log_helper(u"Unable to create episode, please set its status manually: " + exceptions.ex(e),
logger.WARNING)
else:
# Whole season
log_str += _log_helper(u"Setting season to wanted: " + str(season))
for ep_obj in show_obj.getAllEpisodes(season):
log_str += _log_helper(u"Reverting episode (%d, %d): %s" % (season, ep_obj.episode, ep_obj.name))
with ep_obj.lock:
if ep_obj in history_eps:
log_str += _log_helper(u"Found in history")
ep_obj.status = history_eps[ep_obj]['old_status']
else:
log_str += _log_helper(u"WARNING: Episode not found in history. Setting it back to WANTED",
logger.WARNING)
ep_obj.status = WANTED
ep_obj.saveToDB()
return log_str
except EpisodeNotFoundException, e:
logger.log(u"Unable to create episode, please set its status manually: " + ex(e),
logger.WARNING)
return
def markFailed(show_obj, season, episode=None):
def markFailed(epObj):
log_str = u""
if episode:
try:
ep_obj = show_obj.getEpisode(season, episode)
try:
with epObj.lock:
quality = Quality.splitCompositeStatus(epObj.status)[1]
epObj.status = Quality.compositeStatus(FAILED, quality)
epObj.saveToDB()
with ep_obj.lock:
quality = Quality.splitCompositeStatus(ep_obj.status)[1]
ep_obj.status = Quality.compositeStatus(FAILED, quality)
ep_obj.saveToDB()
except exceptions.EpisodeNotFoundException, e:
log_str += _log_helper(u"Unable to get episode, please set its status manually: " + exceptions.ex(e),
logger.WARNING)
else:
# Whole season
for ep_obj in show_obj.getAllEpisodes(season):
with ep_obj.lock:
quality = Quality.splitCompositeStatus(ep_obj.status)[1]
ep_obj.status = Quality.compositeStatus(FAILED, quality)
ep_obj.saveToDB()
except EpisodeNotFoundException, e:
logger.log(u"Unable to get episode, please set its status manually: " + ex(e), logger.WARNING)
return log_str
@ -191,6 +161,7 @@ def logSnatch(searchResult):
logDate = datetime.datetime.today().strftime(dateFormat)
release = prepareFailedName(searchResult.name)
providerClass = searchResult.provider
if providerClass is not None:
provider = providerClass.name
@ -200,13 +171,11 @@ def logSnatch(searchResult):
show_obj = searchResult.episodes[0].show
for episode in searchResult.episodes:
old_status = show_obj.getEpisode(episode.season, episode.episode).status
myDB.action(
"INSERT INTO history (date, size, release, provider, showid, season, episode, old_status)"
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
[logDate, searchResult.size, release, provider, show_obj.indexerid, episode.season, episode.episode,
old_status])
episode.status])
def deleteLoggedSnatch(release, size, provider):
@ -224,13 +193,11 @@ def trimHistory():
(datetime.datetime.today() - datetime.timedelta(days=30)).strftime(dateFormat)))
def findRelease(show, season, episode):
def findRelease(epObj):
"""
Find releases in history by show ID and season.
Return None for release if multiple found or no release found.
"""
if not show: return (None, None, None)
if not season: return (None, None, None)
release = None
provider = None
@ -238,13 +205,13 @@ def findRelease(show, season, episode):
myDB = db.DBConnection("failed.db")
# Clear old snatches for this release if any exist
myDB.action("DELETE FROM history WHERE showid=" + str(show.indexerid) + " AND season=" + str(
season) + " AND episode=" + str(episode) + " AND date < (SELECT max(date) FROM history WHERE showid=" + str(
show.indexerid) + " AND season=" + str(season) + " AND episode=" + str(episode) + ")")
myDB.action("DELETE FROM history WHERE showid=" + str(epObj.show.indexerid) + " AND season=" + str(
epObj.season) + " AND episode=" + str(epObj.episode) + " AND date < (SELECT max(date) FROM history WHERE showid=" + str(
epObj.show.indexerid) + " AND season=" + str(epObj.season) + " AND episode=" + str(epObj.episode) + ")")
# Search for release in snatch history
results = myDB.select("SELECT release, provider, date FROM history WHERE showid=? AND season=? AND episode=?",
[show.indexerid, season, episode])
[epObj.show.indexerid, epObj.season, epObj.episode])
for result in results:
release = str(result["release"])
@ -255,9 +222,9 @@ def findRelease(show, season, episode):
myDB.action("DELETE FROM history WHERE release=? AND date!=?", [release, date])
# Found a previously failed release
logger.log(u"Failed release found for season (%s): (%s)" % (season, result["release"]), logger.DEBUG)
logger.log(u"Failed release found for season (%s): (%s)" % (epObj.season, result["release"]), logger.DEBUG)
return (release, provider)
# Release was not found
logger.log(u"No releases found for season (%s) of (%s)" % (season, show.indexerid), logger.DEBUG)
logger.log(u"No releases found for season (%s) of (%s)" % (epObj.season, epObj.show.indexerid), logger.DEBUG)
return (release, provider)

View file

@ -85,11 +85,11 @@ def logSubtitle(showid, season, episode, status, subtitleResult):
_logHistoryItem(action, showid, season, episode, quality, resource, provider)
def logFailed(indexerid, season, episode, status, release, provider=None):
showid = int(indexerid)
season = int(season)
epNum = int(episode)
status, quality = Quality.splitCompositeStatus(status)
def logFailed(epObj, release, provider=None):
showid = int(epObj.show.indexerid)
season = int(epObj.season)
epNum = int(epObj.episode)
status, quality = Quality.splitCompositeStatus(epObj.status)
action = Quality.compositeStatus(FAILED, quality)
_logHistoryItem(action, showid, season, epNum, quality, release, provider)

View file

@ -625,7 +625,6 @@ class PostProcessor(object):
# detect and convert scene numbered releases
season, cur_episode = sickbeard.scene_numbering.get_indexer_numbering(indexer_id,indexer,season,cur_episode)
self._log(u"Episode object has been scene converted to " + str(season) + "x" + str(cur_episode), logger.DEBUG)
# now that we've figured out which episode this file is just load it manually
try:
@ -634,6 +633,9 @@ class PostProcessor(object):
self._log(u"Unable to create episode: " + ex(e), logger.DEBUG)
raise exceptions.PostProcessingFailed()
self._log(u"Episode object has been converted from Scene numbering " + str(curEp.scene_season) + "x" + str(
curEp.scene_episode) + " to Indexer numbering" + str(curEp.season) + "x" + str(curEp.episode))
# associate all the episodes together under a single root episode
if root_ep == None:
root_ep = curEp

View file

@ -257,21 +257,22 @@ class FailedQueueItem(generic_queue.QueueItem):
def execute(self):
generic_queue.QueueItem.execute(self)
failed_episodes = []
for i, epObj in enumerate(self.episodes):
(release, provider) = failed_history.findRelease(self.show, epObj.season, epObj.episode)
(release, provider) = failed_history.findRelease(epObj)
if release:
logger.log(u"Marking release as bad: " + release)
failed_history.markFailed(self.show, epObj.season, epObj.episode)
failed_history.markFailed(epObj)
failed_history.logFailed(release)
history.logFailed(self.show.indexerid, epObj.season, epObj.episode, epObj.status, release, provider)
failed_history.revertEpisode(self.show, epObj.season, epObj.episode)
history.logFailed(epObj, release, provider)
failed_history.revertEpisode(epObj)
failed_episodes.append(epObj)
try:
logger.log(
"Beginning failed download search for episodes from Season [" + str(self.episodes[0].season) + "]")
searchResult = search.searchProviders(self, self.show, self.episodes[0].season, self.episodes, False, True)
searchResult = search.searchProviders(self, self.show, failed_episodes[0].season, failed_episodes, False, True)
if searchResult:
self.success = SearchQueue().snatch_item(searchResult)

View file

@ -27,6 +27,7 @@ import tarfile
import stat
import traceback
import gh_api as github
import threading
import sickbeard
from sickbeard import helpers
@ -57,23 +58,27 @@ class CheckVersion():
self.updater = None
def run(self):
updated = None
if self.check_for_new_version():
if sickbeard.AUTO_UPDATE:
logger.log(u"New update found for SickRage, starting auto-updater ...")
updated = sickbeard.versionCheckScheduler.action.update()
if updated:
logger.log(u"Update was successfull, restarting SickRage ...")
sickbeard.restart(False)
# refresh scene exceptions too
scene_exceptions.retrieve_exceptions()
# do a soft restart
threading.Timer(2, sickbeard.invoke_restart, [False]).start()
# refresh network timezones
network_timezones.update_network_dict()
if not updated:
# refresh scene exceptions too
scene_exceptions.retrieve_exceptions()
# sure, why not?
if sickbeard.USE_FAILED_DOWNLOADS:
failed_history.trimHistory()
# refresh network timezones
network_timezones.update_network_dict()
# sure, why not?
if sickbeard.USE_FAILED_DOWNLOADS:
failed_history.trimHistory()
def find_install_type(self):
"""