Added ability to switch branches from general settings/advanced, will perform a forced checkout of whatever branch you choose and restart automatically.

Git updates are now forced to overwrite locale changes to avoid needing to manually stash uncommited changes before being able to proceed with a update.

Fixed indexer mapping when unable to locate show on indexer that we are trying to map to.
This commit is contained in:
echel0n 2014-07-27 10:58:19 -07:00
parent 91ee2c9ef5
commit 7dc89c084f
10 changed files with 118 additions and 51 deletions

View file

@ -276,6 +276,23 @@
<fieldset class="component-group-list" style="width:670px"> <fieldset class="component-group-list" style="width:670px">
<div class="field-pair">
<label class="nocheck clearfix">
<span class="component-title">Git Branch:</span>
<span class="component-desc">
<select id="git_branch" name="git_branch">
#for $cur_branch in $sickbeard.versionCheckScheduler.action.list_remote_branches():
<option value="$cur_branch" #if $cur_branch == $sickbeard.version.SICKBEARD_VERSION then "selected=\"selected\"" else ""#>$cur_branch.capitalize()</option>
#end for
</select>
</span>
</label>
<label class="nocheck clearfix">
<span class="component-title">&nbsp;</span>
<span class="component-desc">Select the branch you wish to use, changing this will require a restart.</span>
</label>
</div>
<div class="field-pair"> <div class="field-pair">
<label class="nocheck clearfix"> <label class="nocheck clearfix">
<span class="component-title">CPU Throttling:</span> <span class="component-title">CPU Throttling:</span>

View file

@ -694,8 +694,7 @@ class Tvdb:
log().debug("Searching for show %s" % series) log().debug("Searching for show %s" % series)
self.config['params_getSeries']['seriesname'] = series self.config['params_getSeries']['seriesname'] = series
seriesEt = self._getetsrc(self.config['url_getSeries'], self.config['params_getSeries']) seriesEt = self._getetsrc(self.config['url_getSeries'], self.config['params_getSeries'])
return [seriesEt[item] for item in seriesEt][0] if seriesEt else []
return [seriesEt[item] for item in seriesEt][0]
def _getSeries(self, series): def _getSeries(self, series):
"""This searches TheTVDB.com for the series name, """This searches TheTVDB.com for the series name,
@ -715,7 +714,6 @@ class Tvdb:
log().debug("Using custom UI %s" % (repr(self.config['custom_ui']))) log().debug("Using custom UI %s" % (repr(self.config['custom_ui'])))
CustomUI = self.config['custom_ui'] CustomUI = self.config['custom_ui']
ui = CustomUI(config=self.config) ui = CustomUI(config=self.config)
else: else:
if not self.config['interactive']: if not self.config['interactive']:
log().debug('Auto-selecting first search result using BaseUI') log().debug('Auto-selecting first search result using BaseUI')

View file

@ -556,8 +556,7 @@ class TVRage:
log().debug("Searching for show %s" % series) log().debug("Searching for show %s" % series)
self.config['params_getSeries']['show'] = series self.config['params_getSeries']['show'] = series
seriesEt = self._getetsrc(self.config['url_getSeries'], self.config['params_getSeries']) seriesEt = self._getetsrc(self.config['url_getSeries'], self.config['params_getSeries'])
return [seriesEt[item] for item in seriesEt][0] if seriesEt else []
return [seriesEt[item] for item in seriesEt][0]
def _getSeries(self, series): def _getSeries(self, series):
"""This searches tvrage.com for the series name, """This searches tvrage.com for the series name,

View file

@ -24,7 +24,6 @@ import socket
import os import os
import re import re
from urllib2 import getproxies
from threading import Lock from threading import Lock
# apparently py2exe won't build these unless they're imported somewhere # apparently py2exe won't build these unless they're imported somewhere

View file

@ -199,15 +199,15 @@ class ShowListUI:
self.log = log self.log = log
def selectSeries(self, allSeries): def selectSeries(self, allSeries):
if sickbeard.showList: try:
idList = [x.indexerid for x in sickbeard.showList]
# try to pick a show that's in my show list # try to pick a show that's in my show list
for curShow in allSeries: for curShow in allSeries:
if int(curShow['id']) in idList: if filter(lambda x: int(x.indexerid) == int(curShow['id']), sickbeard.showList):
return curShow return curShow
except:
pass
# if nothing matches then return everything # if nothing matches then return first result
return allSeries[0] return allSeries[0]

View file

@ -27,6 +27,7 @@ from sickbeard import helpers
from sickbeard import logger from sickbeard import logger
from sickbeard import naming from sickbeard import naming
from sickbeard import db from sickbeard import db
from sickbeard import version
naming_ep_type = ("%(seasonnumber)dx%(episodenumber)02d", naming_ep_type = ("%(seasonnumber)dx%(episodenumber)02d",
"s%(seasonnumber)02de%(episodenumber)02d", "s%(seasonnumber)02de%(episodenumber)02d",
@ -190,6 +191,10 @@ def change_VERSION_NOTIFY(version_notify):
if oldSetting == False and version_notify == True: if oldSetting == False and version_notify == True:
sickbeard.versionCheckScheduler.action.run() # @UndefinedVariable sickbeard.versionCheckScheduler.action.run() # @UndefinedVariable
def change_VERSION(version):
if sickbeard.version.SICKBEARD_VERSION != version:
sickbeard.versionCheckScheduler.action.run() # @UndefinedVariable
def CheckSection(CFG, sec): def CheckSection(CFG, sec):
""" Check if INI section exists, if not create it """ """ Check if INI section exists, if not create it """

View file

@ -87,3 +87,14 @@ class GitHub(object):
['repos', self.github_repo_user, self.github_repo, 'compare', base + '...' + head], ['repos', self.github_repo_user, self.github_repo, 'compare', base + '...' + head],
params={'per_page': per_page}) params={'per_page': per_page})
return access_API return access_API
def branches(self):
access_API = self._access_API(
['repos', self.github_repo_user, self.github_repo, 'branches'])
return access_API
def checkout(self, branch):
access_API = self._access_API(
['repos', self.github_repo_user, self.github_repo, 'branches'])
return access_API

View file

@ -65,6 +65,7 @@ from itertools import izip, cycle
urllib._urlopener = classes.SickBeardURLopener() urllib._urlopener = classes.SickBeardURLopener()
def indentXML(elem, level=0): def indentXML(elem, level=0):
''' '''
Does our pretty printing, makes Matt very happy Does our pretty printing, makes Matt very happy
@ -192,12 +193,14 @@ def sanitizeFileName(name):
return name return name
def _remove_file_failed(file): def _remove_file_failed(file):
try: try:
ek.ek(os.remove, file) ek.ek(os.remove, file)
except: except:
pass pass
def findCertainShow(showList, indexerid): def findCertainShow(showList, indexerid):
if not showList: if not showList:
return None return None
@ -522,6 +525,7 @@ def delete_empty_folders(check_empty_dir, keep_dir=None):
else: else:
break break
def fileBitFilter(mode): def fileBitFilter(mode):
for bit in [stat.S_IXUSR, stat.S_IXGRP, stat.S_IXOTH, stat.S_ISUID, stat.S_ISGID]: for bit in [stat.S_IXUSR, stat.S_IXGRP, stat.S_IXOTH, stat.S_ISUID, stat.S_ISGID]:
if mode & bit: if mode & bit:
@ -529,6 +533,7 @@ def fileBitFilter(mode):
return mode return mode
def chmodAsParent(childPath): def chmodAsParent(childPath):
if os.name == 'nt' or os.name == 'ce': if os.name == 'nt' or os.name == 'ce':
return return
@ -567,6 +572,7 @@ def chmodAsParent(childPath):
except OSError: except OSError:
logger.log(u"Failed to set permission for %s to %o" % (childPath, childMode), logger.ERROR) logger.log(u"Failed to set permission for %s to %o" % (childPath, childMode), logger.ERROR)
def fixSetGroupID(childPath): def fixSetGroupID(childPath):
if os.name == 'nt' or os.name == 'ce': if os.name == 'nt' or os.name == 'ce':
return return
@ -622,14 +628,17 @@ def get_absolute_number_from_season_and_episode(show, season, episode):
if len(sqlResults) == 1: if len(sqlResults) == 1:
absolute_number = int(sqlResults[0]["absolute_number"]) absolute_number = int(sqlResults[0]["absolute_number"])
logger.log( logger.log(
"Found absolute_number:" + str(absolute_number) + " by " + str(season) + "x" + str(episode), logger.DEBUG) "Found absolute_number:" + str(absolute_number) + " by " + str(season) + "x" + str(episode),
logger.DEBUG)
else: else:
logger.log( logger.log(
"No entries for absolute number in show: " + show.name + " found using " + str(season) + "x" + str(episode), "No entries for absolute number in show: " + show.name + " found using " + str(season) + "x" + str(
episode),
logger.DEBUG) logger.DEBUG)
return absolute_number return absolute_number
def get_all_episodes_from_absolute_number(show, absolute_numbers, indexer_id=None): def get_all_episodes_from_absolute_number(show, absolute_numbers, indexer_id=None):
episodes = [] episodes = []
season = None season = None
@ -712,11 +721,13 @@ def create_https_certificates(ssl_cert, ssl_key):
return True return True
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
doctest.testmod() doctest.testmod()
def parse_json(data): def parse_json(data):
""" """
Parse json data into a python object Parse json data into a python object
@ -1016,6 +1027,7 @@ def get_show(name, tryIndexers=False):
return showObj return showObj
def is_hidden_folder(folder): def is_hidden_folder(folder):
""" """
Returns True if folder is hidden. Returns True if folder is hidden.
@ -1128,16 +1140,13 @@ def mapIndexersToShow(showObj):
mapped = {showObj.indexer: showObj.indexerid} mapped = {showObj.indexer: showObj.indexerid}
myDB = db.DBConnection() myDB = db.DBConnection()
sqlResults = myDB.select( sqlResults = myDB.select(
"SELECT * FROM indexer_mapping WHERE indexer_id = ? AND indexer = ?", "SELECT * FROM indexer_mapping WHERE indexer_id = ? AND indexer = ?",
[showObj.indexerid, showObj.indexer]) [showObj.indexerid, showObj.indexer])
# for each mapped entry # for each mapped entry
for curResult in sqlResults: for curResult in sqlResults:
logger.log(u"Found " + sickbeard.indexerApi(showObj.indexer).name + "<->" + sickbeard.indexerApi( logger.log(u"Found indexer mapping in cache for show: " + showObj.name, logger.DEBUG)
int(curResult['mindexer'])).name + " mapping in cache for show: " + showObj.name, logger.DEBUG)
mapped[int(curResult['mindexer'])] = int(curResult['mindexer_id']) mapped[int(curResult['mindexer'])] = int(curResult['mindexer_id'])
else: else:
sql_l = [] sql_l = []
@ -1150,22 +1159,27 @@ def mapIndexersToShow(showObj):
lINDEXER_API_PARMS['custom_ui'] = classes.ShowListUI lINDEXER_API_PARMS['custom_ui'] = classes.ShowListUI
t = sickbeard.indexerApi(indexer).indexer(**lINDEXER_API_PARMS) t = sickbeard.indexerApi(indexer).indexer(**lINDEXER_API_PARMS)
try:
mapped_show = t[showObj.name] mapped_show = t[showObj.name]
except sickbeard.indexer_shownotfound:
logger.log(u"Unable to map " + sickbeard.indexerApi(showObj.indexer).name + "->" + sickbeard.indexerApi(
indexer).name + " for show: " + showObj.name + ", skipping it", logger.ERROR)
mapped_show = None
if len(mapped_show) and not len(mapped_show) > 1: if len(mapped_show) and not len(mapped_show) > 1:
logger.log(u"Mapping " + sickbeard.indexerApi(showObj.indexer).name + "<->" + sickbeard.indexerApi( logger.log(u"Mapping " + sickbeard.indexerApi(showObj.indexer).name + "->" + sickbeard.indexerApi(
indexer).name + " for show " + showObj.name, indexer).name + " for show: " + showObj.name, logger.DEBUG)
logger.DEBUG)
mapped[indexer] = int(mapped_show[0]['id']) mapped[indexer] = int(mapped_show[0]['id'])
logger.log(u"Adding " + sickbeard.indexerApi(showObj.indexer).name + "<->" + sickbeard.indexerApi( logger.log(u"Adding indexer mapping to DB for show: " + showObj.name, logger.DEBUG)
indexer).name + " mapping to DB for show: " + showObj.name, logger.DEBUG)
sql_l.append([ sql_l.append([
"INSERT OR IGNORE INTO indexer_mapping (indexer_id, indexer, mindexer_id, mindexer) VALUES (?,?,?,?)", "INSERT OR IGNORE INTO indexer_mapping (indexer_id, indexer, mindexer_id, mindexer) VALUES (?,?,?,?)",
[showObj.indexerid, showObj.indexer, int(mapped_show[0]['id']), indexer]]) [showObj.indexerid, showObj.indexer, int(mapped_show[0]['id']), indexer]])
if len(sql_l) > 0: if len(sql_l) > 0:
myDB = db.DBConnection()
myDB.mass_action(sql_l) myDB.mass_action(sql_l)
return mapped return mapped
@ -1183,6 +1197,7 @@ def touchFile(fname, atime=None):
return False return False
def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=None, json=False): def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=None, json=False):
""" """
Returns a byte-string retrieved from the url provider. Returns a byte-string retrieved from the url provider.
@ -1231,10 +1246,7 @@ def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=N
logger.log(u"Unknown exception while loading URL " + url + ": " + traceback.format_exc(), logger.WARNING) logger.log(u"Unknown exception while loading URL " + url + ": " + traceback.format_exc(), logger.WARNING)
return return
if not resp: if not resp.ok:
logger.log(u"No data returned from " + url, logger.DEBUG)
return
elif not resp.ok:
logger.log(u"Requested url " + url + " returned status code is " + str( logger.log(u"Requested url " + url + " returned status code is " + str(
resp.status_code) + ': ' + clients.http_error_code[resp.status_code], logger.WARNING) resp.status_code) + ': ' + clients.http_error_code[resp.status_code], logger.WARNING)
return return
@ -1244,8 +1256,8 @@ def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=N
return resp.content return resp.content
def download_file(url, filename, session=None):
def download_file(url, filename, session=None):
# create session # create session
session = CacheControl(sess=session, cache=caches.FileCache(os.path.join(sickbeard.CACHE_DIR, 'sessions'))) session = CacheControl(sess=session, cache=caches.FileCache(os.path.join(sickbeard.CACHE_DIR, 'sessions')))
@ -1309,6 +1321,7 @@ def download_file(url, filename, session=None):
return True return True
def clearCache(force=False): def clearCache(force=False):
update_datetime = datetime.datetime.now() update_datetime = datetime.datetime.now()

View file

@ -27,7 +27,6 @@ import tarfile
import stat import stat
import traceback import traceback
import gh_api as github import gh_api as github
import threading
import sickbeard import sickbeard
from sickbeard import helpers, notifiers from sickbeard import helpers, notifiers
@ -53,11 +52,8 @@ class CheckVersion():
else: else:
self.updater = None self.updater = None
def __del__(self):
pass
def run(self, force=False): def run(self, force=False):
if self.check_for_new_version(): if self.check_for_new_version(force):
if sickbeard.AUTO_UPDATE: if sickbeard.AUTO_UPDATE:
logger.log(u"New update found for SickRage, starting auto-updater ...") logger.log(u"New update found for SickRage, starting auto-updater ...")
ui.notifications.message('New update found for SickRage, starting auto-updater') ui.notifications.message('New update found for SickRage, starting auto-updater')
@ -113,10 +109,15 @@ class CheckVersion():
self.updater.set_newest_text() self.updater.set_newest_text()
return True return True
def update(self): def update(self, branch=None):
if self.updater.need_update(): if branch and branch != self.updater.branch:
return self.updater.update(branch)
elif self.updater.need_update():
return self.updater.update() return self.updater.update()
def list_remote_branches(self):
return self.updater.list_remote_branches()
class UpdateManager(): class UpdateManager():
def get_github_repo_user(self): def get_github_repo_user(self):
return 'echel0n' return 'echel0n'
@ -127,7 +128,6 @@ class UpdateManager():
def get_update_url(self): def get_update_url(self):
return sickbeard.WEB_ROOT + "/home/update/?pid=" + str(sickbeard.PID) return sickbeard.WEB_ROOT + "/home/update/?pid=" + str(sickbeard.PID)
class WindowsUpdateManager(UpdateManager): class WindowsUpdateManager(UpdateManager):
def __init__(self): def __init__(self):
self.github_repo_user = self.get_github_repo_user() self.github_repo_user = self.get_github_repo_user()
@ -200,7 +200,10 @@ class WindowsUpdateManager(UpdateManager):
sickbeard.NEWEST_VERSION_STRING = newest_text sickbeard.NEWEST_VERSION_STRING = newest_text
def update(self): def update(self, branch='windows_binaries'):
# set branch version
self.branch = branch
zip_download_url = self._find_newest_version(True) zip_download_url = self._find_newest_version(True)
logger.log(u"new_link: " + repr(zip_download_url), logger.DEBUG) logger.log(u"new_link: " + repr(zip_download_url), logger.DEBUG)
@ -267,6 +270,8 @@ class WindowsUpdateManager(UpdateManager):
return True return True
def list_remote_branches(self):
return ['windows_binaries']
class GitUpdateManager(UpdateManager): class GitUpdateManager(UpdateManager):
def __init__(self): def __init__(self):
@ -500,13 +505,19 @@ class GitUpdateManager(UpdateManager):
return False return False
def update(self): def update(self, branch=sickbeard.version.SICKBEARD_VERSION):
""" """
Calls git pull origin <branch> in order to update SickRage. Returns a bool depending Calls git pull origin <branch> in order to update SickRage. Returns a bool depending
on the call's success. on the call's success.
""" """
output, err, exit_status = self._run_git(self._git_path, 'pull origin ' + self.branch) # @UnusedVariable # set branch version
self.branch = branch
if self.branch == sickbeard.version.SICKBEARD_VERSION:
output, err, exit_status = self._run_git(self._git_path, 'pull -f origin ' + self.branch) # @UnusedVariable
else:
output, err, exit_status = self._run_git(self._git_path, 'checkout -f ' + self.branch) # @UnusedVariable
if exit_status == 0: if exit_status == 0:
# Notify update successful # Notify update successful
@ -516,6 +527,11 @@ class GitUpdateManager(UpdateManager):
return False return False
def list_remote_branches(self):
branches, err, exit_status = self._run_git(self._git_path, 'branch -r') # @UnusedVariable
if exit_status == 0 and branches:
return branches.strip().replace('origin/', '').split()
return []
class SourceUpdateManager(UpdateManager): class SourceUpdateManager(UpdateManager):
def __init__(self): def __init__(self):
@ -629,10 +645,14 @@ class SourceUpdateManager(UpdateManager):
sickbeard.NEWEST_VERSION_STRING = newest_text sickbeard.NEWEST_VERSION_STRING = newest_text
def update(self): def update(self, branch=sickbeard.version.SICKBEARD_VERSION):
""" """
Downloads the latest source tarball from github and installs it over the existing version. Downloads the latest source tarball from github and installs it over the existing version.
""" """
# set branch version
self.branch = branch
base_url = 'http://github.com/' + self.github_repo_user + '/' + self.github_repo base_url = 'http://github.com/' + self.github_repo_user + '/' + self.github_repo
tar_download_url = base_url + '/tarball/' + self.branch tar_download_url = base_url + '/tarball/' + self.branch
version_path = ek.ek(os.path.join, sickbeard.PROG_DIR, u'version.txt') version_path = ek.ek(os.path.join, sickbeard.PROG_DIR, u'version.txt')
@ -721,3 +741,7 @@ class SourceUpdateManager(UpdateManager):
notifiers.notify_git_update(sickbeard.NEWEST_VERSION_STRING) notifiers.notify_git_update(sickbeard.NEWEST_VERSION_STRING)
return True return True
def list_remote_branches(self):
gh = github.GitHub(self.github_repo_user, self.github_repo, self.branch)
return gh.branches()

View file

@ -47,6 +47,7 @@ from sickbeard import naming
from sickbeard import scene_exceptions from sickbeard import scene_exceptions
from sickbeard import subtitles from sickbeard import subtitles
from sickbeard import network_timezones from sickbeard import network_timezones
from sickbeard import version
from sickbeard.providers import newznab, rsstorrent from sickbeard.providers import newznab, rsstorrent
from sickbeard.common import Quality, Overview, statusStrings, qualityPresetStrings, cpu_presets, SKIPPED from sickbeard.common import Quality, Overview, statusStrings, qualityPresetStrings, cpu_presets, SKIPPED
@ -604,6 +605,13 @@ class ManageSearches(MainHandler):
return _munge(t) return _munge(t)
def forceVersionCheck(self, *args, **kwargs):
# force a check to see if there is a new version
if sickbeard.versionCheckScheduler.action.check_for_new_version(force=True):
logger.log(u"Forcing version check")
redirect("/home/")
def forceBacklog(self, *args, **kwargs): def forceBacklog(self, *args, **kwargs):
# force it to run the next time it looks # force it to run the next time it looks
result = sickbeard.backlogSearchScheduler.forceRun() result = sickbeard.backlogSearchScheduler.forceRun()
@ -613,7 +621,6 @@ class ManageSearches(MainHandler):
redirect("/manage/manageSearches/") redirect("/manage/manageSearches/")
def forceSearch(self, *args, **kwargs): def forceSearch(self, *args, **kwargs):
# force it to run the next time it looks # force it to run the next time it looks
@ -1429,7 +1436,7 @@ class ConfigGeneral(MainHandler):
handle_reverse_proxy=None, sort_article=None, auto_update=None, notify_on_update=None, handle_reverse_proxy=None, sort_article=None, auto_update=None, notify_on_update=None,
proxy_setting=None, anon_redirect=None, git_path=None, calendar_unprotected=None, proxy_setting=None, anon_redirect=None, git_path=None, calendar_unprotected=None,
fuzzy_dating=None, trim_zero=None, date_preset=None, date_preset_na=None, time_preset=None, fuzzy_dating=None, trim_zero=None, date_preset=None, date_preset_na=None, time_preset=None,
indexer_timeout=None, play_videos=None): indexer_timeout=None, play_videos=None, git_branch=None):
results = [] results = []
@ -1496,8 +1503,11 @@ class ConfigGeneral(MainHandler):
sickbeard.HANDLE_REVERSE_PROXY = config.checkbox_to_value(handle_reverse_proxy) sickbeard.HANDLE_REVERSE_PROXY = config.checkbox_to_value(handle_reverse_proxy)
sickbeard.save_config() sickbeard.save_config()
sickbeard.versionCheckScheduler.action.checkout_branch(git_branch)
if len(results) > 0: if len(results) > 0:
for x in results: for x in results:
logger.log(x, logger.ERROR) logger.log(x, logger.ERROR)
@ -2535,15 +2545,6 @@ class HomePostProcess(MainHandler):
t.submenu = HomeMenu() t.submenu = HomeMenu()
return _munge(t) return _munge(t)
def forceVersionCheck(self, *args, **kwargs):
# force a check to see if there is a new version
if sickbeard.versionCheckScheduler.action.check_for_new_version(force=True):
logger.log(u"Forcing version check")
redirect("/home/")
def processEpisode(self, dir=None, nzbName=None, jobName=None, quiet=None, process_method=None, force=None, def processEpisode(self, dir=None, nzbName=None, jobName=None, quiet=None, process_method=None, force=None,
is_priority=None, failed="0", type="auto", *args, **kwargs): is_priority=None, failed="0", type="auto", *args, **kwargs):