diff --git a/SickBeard.py b/SickBeard.py index 2beb507455319dd660f8e3f3c5a12601cddc144f..ae44059e17fedc6f6eb35a0f99ff7b8d85e486c2 100644 --- a/SickBeard.py +++ b/SickBeard.py @@ -65,6 +65,9 @@ def loadShowsFromDB(): def main(): + # use this pid for everything + sickbeard.PID = os.getpid() + # do some preliminary stuff sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(sys.argv[0])) sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME) @@ -153,10 +156,10 @@ def main(): 'password': sickbeard.WEB_PASSWORD, }) except IOError: - logger.log(u"Unable to start web server, is something else running on port %d?" % sickbeard.WEB_PORT, logger.ERROR) + logger.log(u"Unable to start web server, is something else running on port %d?" % startPort, logger.ERROR) if sickbeard.LAUNCH_BROWSER: logger.log(u"Launching browser and exiting", logger.ERROR) - sickbeard.launchBrowser() + sickbeard.launchBrowser(startPort) sys.exit() # build from the DB to start with @@ -172,7 +175,7 @@ def main(): # launch browser if we're supposed to if sickbeard.LAUNCH_BROWSER: - sickbeard.launchBrowser() + sickbeard.launchBrowser(startPort) # start an update if we're supposed to if forceUpdate: diff --git a/data/css/tablesorter.css b/data/css/tablesorter.css index b88f1dd1179e347b2b6315a11ec62ff8e7bb3867..30259c19c173656c0adcc4e88aba93bcc8ba7e66 100644 --- a/data/css/tablesorter.css +++ b/data/css/tablesorter.css @@ -1,15 +1,15 @@ /* tables */ table.tablesorter thead tr .header { - background-image: url(/images/tablesorter/bg.gif); + background-image: url(../images/tablesorter/bg.gif); background-repeat: no-repeat; background-position: center right; cursor: pointer; } table.tablesorter thead tr .headerSortUp { - background-image: url(/images/tablesorter/asc.gif); + background-image: url(../images/tablesorter/asc.gif); } table.tablesorter thead tr .headerSortDown { - background-image: url(/images/tablesorter/desc.gif); + background-image: url(../images/tablesorter/desc.gif); } table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp { background-color: #57442B; diff --git a/data/interfaces/default/config_general.tmpl b/data/interfaces/default/config_general.tmpl index ab67e23942c640bb76b782d9f5937ca9e14a43b8..d2fdb0b28f9517df5de0786bc06c7af25fa77b9d 100644 --- a/data/interfaces/default/config_general.tmpl +++ b/data/interfaces/default/config_general.tmpl @@ -203,6 +203,12 @@ the settings in your existing shows!</strong><br /> <legend>Episode Naming</legend> +<strong>Season Folders format</strong><br /> +Format to use when creating season folders<br /> +Eg. 'Season %0d' or 'season%02d'<br /> +<input type="text" name="season_folders_format" value="$sickbeard.SEASON_FOLDERS_FORMAT" size="10"><br /> +<br /> + <label for="naming_show_name"><input type="checkbox" name="naming_show_name" id="naming_show_name" #if $sickbeard.NAMING_SHOW_NAME then "CHECKED" else ""#> <strong>Include show name</strong></label><br /> <br /> <label for="naming_ep_name"><input type="checkbox" name="naming_ep_name" id="naming_ep_name" #if $sickbeard.NAMING_EP_NAME then "CHECKED" else ""#> <strong>Include episode name</strong></label><br /> diff --git a/data/interfaces/default/inc_bottom.tmpl b/data/interfaces/default/inc_bottom.tmpl index 7303bf7d16ca5c106969602f630842995ce611b2..8e268b372280bebe9c2457328f7d2a155351ea95 100644 --- a/data/interfaces/default/inc_bottom.tmpl +++ b/data/interfaces/default/inc_bottom.tmpl @@ -13,7 +13,7 @@ <br /> <b>Search</b>: <%=str(sickbeard.currentSearchScheduler.timeLeft()).split('.')[0]%> | <!--<b>Update</b>: <a%a=str(sickbeard.updateScheduler.timeLeft()).split('.')[0]%> | --> -<b>Backlog</b>: $sickbeard.backlogSearchScheduler.nextRun().strftime("%A") <br /> +<b>Backlog</b>: $sickbeard.backlogSearchScheduler.nextRun().strftime("%a %b %d") <br /> </div> </body> </html> diff --git a/lib/tvnamer/utils.py b/lib/tvnamer/utils.py index 8e9761e801632f7d3b26fcefec240f76578af7b0..c572dc0e29539653449b8d85548d775fe60cf606 100644 --- a/lib/tvnamer/utils.py +++ b/lib/tvnamer/utils.py @@ -312,7 +312,10 @@ class FileParser(object): month = day day = tmp_month - episodenumbers = [datetime.date(int(match.group('year')), month, day)] + try: + episodenumbers = [datetime.date(int(match.group('year')), month, day)] + except ValueError, e: + raise InvalidFilename(str(e)) else: raise ConfigValueError( diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 133e7e75db94ff5498b10d6571ee13bf919d0d4c..da8587cae09cd557319b76811542365c067ec375 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -43,6 +43,8 @@ from lib.configobj import ConfigObj SOCKET_TIMEOUT = 30 +PID = None + CFG = None CONFIG_FILE = None @@ -99,6 +101,7 @@ ART_THUMBNAILS = None ART_SEASON_THUMBNAILS = None QUALITY_DEFAULT = None +SEASON_FOLDERS_FORMAT = None SEASON_FOLDERS_DEFAULT = None PROVIDER_ORDER = [] @@ -289,7 +292,7 @@ def initialize(consoleLogging=True): airingList, comingList, loadingShowList, SOCKET_TIMEOUT, \ NZBS, NZBS_UID, NZBS_HASH, USE_NZB, USE_TORRENT, TORRENT_DIR, USENET_RETENTION, \ SEARCH_FREQUENCY, DEFAULT_SEARCH_FREQUENCY, BACKLOG_SEARCH_FREQUENCY, \ - DEFAULT_BACKLOG_SEARCH_FREQUENCY, QUALITY_DEFAULT, SEASON_FOLDERS_DEFAULT, \ + DEFAULT_BACKLOG_SEARCH_FREQUENCY, QUALITY_DEFAULT, SEASON_FOLDERS_FORMAT, SEASON_FOLDERS_DEFAULT, \ USE_GROWL, GROWL_HOST, GROWL_PASSWORD, PROG_DIR, NZBMATRIX, NZBMATRIX_USERNAME, \ NZBMATRIX_APIKEY, versionCheckScheduler, VERSION_NOTIFY, PROCESS_AUTOMATICALLY, \ KEEP_PROCESSED_DIR, TV_DOWNLOAD_DIR, TVDB_BASE_URL, MIN_SEARCH_FREQUENCY, \ @@ -339,6 +342,9 @@ def initialize(consoleLogging=True): LAUNCH_BROWSER = bool(check_setting_int(CFG, 'General', 'launch_browser', 1)) CACHE_DIR = check_setting_str(CFG, 'General', 'cache_dir', 'cache') + # fix bad configs due to buggy code + if CACHE_DIR == 'None': + CACHE_DIR = 'cache' if not helpers.makeDir(CACHE_DIR): logger.log(u"!!! Creating local cache dir failed, using system default", logger.ERROR) CACHE_DIR = None @@ -353,6 +359,7 @@ def initialize(consoleLogging=True): QUALITY_DEFAULT = check_setting_int(CFG, 'General', 'quality_default', SD) VERSION_NOTIFY = check_setting_int(CFG, 'General', 'version_notify', 1) + SEASON_FOLDERS_FORMAT = check_setting_str(CFG, 'General', 'season_folders_format', 'Season %02d') SEASON_FOLDERS_DEFAULT = bool(check_setting_int(CFG, 'General', 'season_folders_default', 0)) PROVIDER_ORDER = check_setting_str(CFG, 'General', 'provider_order', '').split() @@ -670,10 +677,10 @@ def saveAndShutdown(restart=False): elif install_type == 'win': if hasattr(sys, 'frozen'): # c:\dir\to\updater.exe 12345 c:\dir\to\sickbeard.exe - popen_list = [os.path.join(sickbeard.PROG_DIR, 'updater.exe'), str(os.getpid()), sys.executable] + popen_list = [os.path.join(sickbeard.PROG_DIR, 'updater.exe'), str(sickbeard.PID), sys.executable] else: logger.log(u"Unknown SB launch method, please file a bug report about this", logger.ERROR) - popen_list = [sys.executable, os.path.join(sickbeard.PROG_DIR, 'updater.py'), str(os.getpid()), sys.executable, sickbeard.MY_FULLNAME ] + popen_list = [sys.executable, os.path.join(sickbeard.PROG_DIR, 'updater.py'), str(sickbeard.PID), sys.executable, sickbeard.MY_FULLNAME ] if popen_list: popen_list += sickbeard.MY_ARGS @@ -719,6 +726,7 @@ def save_config(): new_config['General']['use_nzb'] = int(USE_NZB) new_config['General']['download_propers'] = int(DOWNLOAD_PROPERS) new_config['General']['quality_default'] = int(QUALITY_DEFAULT) + new_config['General']['season_folders_format'] = SEASON_FOLDERS_FORMAT new_config['General']['season_folders_default'] = int(SEASON_FOLDERS_DEFAULT) new_config['General']['provider_order'] = ' '.join([x.getID() for x in providers.sortedProviderList()]) new_config['General']['version_notify'] = int(VERSION_NOTIFY) @@ -739,7 +747,7 @@ def save_config(): new_config['General']['art_fanart'] = int(ART_FANART) new_config['General']['art_thumbnails'] = int(ART_THUMBNAILS) new_config['General']['art_season_thumbnails'] = int(ART_SEASON_THUMBNAILS) - new_config['General']['cache_dir'] = CACHE_DIR + new_config['General']['cache_dir'] = CACHE_DIR if CACHE_DIR else 'cache' new_config['General']['tv_download_dir'] = TV_DOWNLOAD_DIR new_config['General']['keep_processed_dir'] = int(KEEP_PROCESSED_DIR) new_config['General']['process_automatically'] = int(PROCESS_AUTOMATICALLY) @@ -817,8 +825,10 @@ def save_config(): new_config.write() -def launchBrowser(): - browserURL = 'http://localhost:%d%s' % (WEB_PORT, WEB_ROOT) +def launchBrowser(startPort=None): + if not startPort: + startPort = WEB_PORT + browserURL = 'http://localhost:%d%s' % (startPort, WEB_ROOT) try: webbrowser.open(browserURL, 2, 1) except: diff --git a/sickbeard/common.py b/sickbeard/common.py index a0bb08dbca68744b9a29e56817ad635c21048be1..bd5f2e934f4d37b57501b69618f185165bebd9bc 100644 --- a/sickbeard/common.py +++ b/sickbeard/common.py @@ -275,6 +275,7 @@ sceneExceptions = {72546: ['CSI'], 194751: ['Conan', 'Conan (2010)'], 164451: ['Carlos (2010)'], 70726: ['Babylon 5', 'Babylon5'], + 83714: ['Genius', 'Genius With Dave Gormand'], } countryList = {'Australia': 'AU', diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index 6767a5d5d549b71af5e257e3ce5edff0f2a77738..ab9b5caf4d229d25d1823aac1fea6a214f196258 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -292,7 +292,7 @@ def buildNFOXML(myShow): def searchDBForShow(regShowName): - showNames = set([regShowName+'%', regShowName.replace(' ','_')+'%']) + showNames = [regShowName.replace(' ','_')] # if tvdb fails then try looking it up in the db myDB = db.DBConnection() diff --git a/sickbeard/logger.py b/sickbeard/logger.py index e0c8d88fc67abcca75cc11432b43da7af55a4bc4..864f3167e44cb45084770c001d619c4e160bfd01 100644 --- a/sickbeard/logger.py +++ b/sickbeard/logger.py @@ -83,17 +83,20 @@ def log(toLog, logLevel=MESSAGE): outLine = message.encode('utf-8') sbLogger = logging.getLogger('sickbeard') - - if logLevel == DEBUG: - sbLogger.debug(outLine) - elif logLevel == MESSAGE: - sbLogger.info(outLine) - elif logLevel == WARNING: - sbLogger.warning(outLine) - elif logLevel == ERROR: - sbLogger.error(outLine) - - # add errors to the UI logger - classes.ErrorViewer.add(classes.UIError(message)) - else: - sbLogger.log(logLevel, outLine) + + try: + if logLevel == DEBUG: + sbLogger.debug(outLine) + elif logLevel == MESSAGE: + sbLogger.info(outLine) + elif logLevel == WARNING: + sbLogger.warning(outLine) + elif logLevel == ERROR: + sbLogger.error(outLine) + + # add errors to the UI logger + classes.ErrorViewer.add(classes.UIError(message)) + else: + sbLogger.log(logLevel, outLine) + except ValueError, e: + pass \ No newline at end of file diff --git a/sickbeard/metadata/__init__.py b/sickbeard/metadata/__init__.py index d135fa35a8ea6a950f48dd6d406d6791ed0092a4..f471ad632a42dd1e2ca8d7c638919303061cc6d6 100644 --- a/sickbeard/metadata/__init__.py +++ b/sickbeard/metadata/__init__.py @@ -1,7 +1,7 @@ -__all__ = ['generic', 'helpers', 'xbmc', 'mediabrowser'] +__all__ = ['generic', 'helpers', 'xbmc', 'mediabrowser', 'ps3'] import sys -import xbmc, mediabrowser +import xbmc, mediabrowser, ps3 def available_generators(): return filter(lambda x: x not in ('generic', 'helpers'), __all__) @@ -32,4 +32,4 @@ def getMetadataGeneratorList(): result[cur_generator_id] = cur_module.name return result - \ No newline at end of file + diff --git a/sickbeard/metadata/ps3.py b/sickbeard/metadata/ps3.py new file mode 100644 index 0000000000000000000000000000000000000000..67a775032b9baf8330103dbdbd4e2db509cf2bcc --- /dev/null +++ b/sickbeard/metadata/ps3.py @@ -0,0 +1,64 @@ +# Author: Nic Wolfe <nic@wolfeden.ca> +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Sick Beard is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. + +import datetime + +import sickbeard + +import generic + +from sickbeard.common import * +from sickbeard import logger, exceptions, helpers +from lib.tvdb_api import tvdb_api, tvdb_exceptions + +from sickbeard import encodingKludge as ek + +class PS3Metadata(generic.GenericMetadata): + """ + Metadata generation class for Sony PS3. + + The following file structure is used: + + show_root/cover.jpg (poster) + show_root/Season 01/show - 1x01 - episode.avi (existing video) + show_root/Season 01/show - 1x01 - episode.avi.cover.jpg (episode thumb) + """ + + def __init__(self): + generic.GenericMetadata.__init__(self) + + self.poster_name = 'cover.jpg' + self.name = 'PS3' + + def get_episode_thumb_path(self, ep_obj): + """ + Returns the path where the episode thumbnail should be stored. Defaults to + the same path as the episode file but with a .cover.jpg extension. + + ep_obj: a TVEpisode instance for which to create the thumbnail + """ + if ek.ek(os.path.isfile, ep_obj.location): + tbn_filename = ep_obj.location + '.cover.jpg' + else: + return None + + return tbn_filename + +# present a standard "interface" +metadata_class = PS3Metadata + diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py index d3edbd605bf3446bc4cfd85bf8495bef1245de57..9800bc94a6c5ddd73b56973cf38c0285e8c3e2df 100644 --- a/sickbeard/processTV.py +++ b/sickbeard/processTV.py @@ -20,12 +20,10 @@ from __future__ import with_statement import os, subprocess, shlex, os.path import shutil -import sys import re import glob from shutil import Error -from sickbeard import notifiers from sickbeard import exceptions from sickbeard import notifiers from sickbeard import db, classes, helpers, sceneHelpers @@ -58,7 +56,7 @@ def renameFile(movedFilePath, newName): try: ek.ek(os.rename, movedFilePath, renamedFilePathname) except (OSError, IOError), e: - logger.log(u"Failed renaming " + movedFilePath + " to " + os.path.basename(renamedFilePathname) + ": " + str(e), logger.ERROR) + logger.log(u"Failed renaming " + movedFilePath + " to " + ek.ek(os.path.basename, renamedFilePathname) + ": " + str(e), logger.ERROR) return False return renamedFilePathname @@ -87,9 +85,9 @@ def deleteAssociatedFiles(file): for associatedFilePath in ek.ek(glob.glob, baseName+'*'): # only delete it if the only non-shared part is the extension if '.' in associatedFilePath[len(baseName):]: - logger.log(u"Not deleting file "+associatedFilePath+" because it looks like it's not related", logger.DEBUG) + logger.log(u"Not deleting file "+associatedFilePath+u" because it looks like it's not related", logger.DEBUG) continue - logger.log(u"Deleting file "+associatedFilePath+" because it is associated with "+file, logger.DEBUG) + logger.log(u"Deleting file "+associatedFilePath+u" because it is associated with "+file, logger.DEBUG) ek.ek(os.remove, associatedFilePath) def _checkForExistingFile(renamedFilePath, oldFile): @@ -130,14 +128,14 @@ def findInHistory(nzbName): for cur_result in sqlResults: episodes.append(int(cur_result["episode"])) - return (tvdb_id, season, episodes) + return (tvdb_id, season, list(set(episodes))) return None def logHelper (logMessage, logLevel=logger.MESSAGE): logger.log(logMessage, logLevel) - return logMessage + "\n" + return logMessage + u"\n" def processDir (dirName, nzbName=None, recurse=False): @@ -151,9 +149,9 @@ def processDir (dirName, nzbName=None, recurse=False): dirName = ek.ek(os.path.realpath, dirName) # if they've got a download dir configured then use it - elif sickbeard.TV_DOWNLOAD_DIR and os.path.isdir(sickbeard.TV_DOWNLOAD_DIR) \ - and os.path.normpath(dirName) != os.path.normpath(sickbeard.TV_DOWNLOAD_DIR): - dirName = ek.ek(os.path.join, sickbeard.TV_DOWNLOAD_DIR, os.path.abspath(dirName).split(os.path.sep)[-1]) + elif sickbeard.TV_DOWNLOAD_DIR and ek.ek(os.path.isdir, sickbeard.TV_DOWNLOAD_DIR) \ + and ek.ek(os.path.normpath, dirName) != ek.ek(os.path.normpath, sickbeard.TV_DOWNLOAD_DIR): + dirName = ek.ek(os.path.join, sickbeard.TV_DOWNLOAD_DIR, ek.ek(os.path.abspath, dirName).split(os.path.sep)[-1]) returnStr += logHelper(u"Trying to use folder "+dirName, logger.DEBUG) # if we didn't find a real dir then quit @@ -162,13 +160,13 @@ def processDir (dirName, nzbName=None, recurse=False): return returnStr # TODO: check if it's failed and deal with it if it is - if os.path.basename(dirName).startswith('_FAILED_'): + if ek.ek(os.path.basename, dirName).startswith('_FAILED_'): returnStr += logHelper(u"The directory name indicates it failed to extract, cancelling", logger.DEBUG) return returnStr - elif os.path.basename(dirName).startswith('_UNDERSIZED_'): + elif ek.ek(os.path.basename, dirName).startswith('_UNDERSIZED_'): returnStr += logHelper(u"The directory name indicates that it was previously rejected for being undersized, cancelling", logger.DEBUG) return returnStr - elif os.path.basename(dirName).startswith('_UNPACK_'): + elif ek.ek(os.path.basename, dirName).startswith('_UNPACK_'): returnStr += logHelper(u"The directory name indicates that this release is in the process of being unpacked, skipping", logger.DEBUG) return returnStr @@ -200,7 +198,7 @@ def processDir (dirName, nzbName=None, recurse=False): # if there's only one video file in the dir we can use the dirname to process too if len(videoFiles) == 1: - returnStr += logHelper(u"Auto processing file: "+movedFilePath+" ("+dirName+")") + returnStr += logHelper(u"Auto processing file: "+movedFilePath+u" ("+dirName+u")") result = processFile(movedFilePath, dirName, nzbName) # as long as the postprocessing was successful delete the old folder unless the config wants us not to @@ -208,7 +206,7 @@ def processDir (dirName, nzbName=None, recurse=False): returnStr += result[0] if not sickbeard.KEEP_PROCESSED_DIR and \ - os.path.normpath(dirName) != os.path.normpath(sickbeard.TV_DOWNLOAD_DIR) and \ + ek.ek(os.path.normpath, dirName) != ek.ek(os.path.normpath, sickbeard.TV_DOWNLOAD_DIR) and \ len(remainingFolders) == 0: returnStr += logHelper(u"Deleting folder " + dirName, logger.DEBUG) @@ -239,13 +237,18 @@ def processDir (dirName, nzbName=None, recurse=False): def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): + logger.log(u"fileName: "+repr(fileName)+u" downloadDir: "+repr(downloadDir)+u" nzbName: "+repr(nzbName), logger.DEBUG) + returnStr = '' - folderName = None + folderName = '' if downloadDir != None: folderName = downloadDir.split(os.path.sep)[-1] - returnStr += logHelper(u"Processing file "+fileName+" (with folder name "+str(folderName)+" and NZB name "+str(nzbName)+")", logger.DEBUG) + if not nzbName: + nzbName = '' + + returnStr += logHelper(u"Processing file "+fileName+u" (with folder name "+folderName+u" and NZB name "+nzbName+u")", logger.DEBUG) finalNameList = [] @@ -267,14 +270,14 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): historyResult = findInHistory(curName) if historyResult: - returnStr += logHelper(u"Result from history: "+str(historyResult)+" from "+curName, logger.DEBUG) + returnStr += logHelper(u"Result from history: "+str(historyResult)+u" from "+curName, logger.DEBUG) (tvdb_id, season, episodes) = historyResult showResults = helpers.findCertainShow(sickbeard.showList, tvdb_id) break # if we're parsing a multi-file folder then the folder name doesn't reflect the correct episode so ignore it if multi_file and episodes: - returnStr += logHelper(u"Multi-file dir "+downloadDir+" doesn't reflect all episode names, only using name & season", logger.DEBUG) + returnStr += logHelper(u"Multi-file dir "+downloadDir+u" doesn't reflect all episode names, only using name & season", logger.DEBUG) episodes = [] # if that didn't work then try manually parsing and searching them on TVDB @@ -299,18 +302,18 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): season = result.seasonnumber if result.seasonnumber != None else 1 episodes = result.episodenumbers - returnStr += logHelper(u"Ended up with season "+str(season)+" and episodes "+str(episodes), logger.DEBUG) + returnStr += logHelper(u"Ended up with season "+str(season)+u" and episodes "+str(episodes), logger.DEBUG) except tvnamer_exceptions.InvalidFilename: - returnStr += logHelper(u"Unable to parse the filename "+curName+" into a valid episode", logger.DEBUG) + returnStr += logHelper(u"Unable to parse the filename "+curName+u" into a valid episode", logger.DEBUG) continue if not result.seriesname: - returnStr += logHelper(u"Filename "+curName+" has no series name, unable to use this name for processing", logger.DEBUG) + returnStr += logHelper(u"Filename "+curName+u" has no series name, unable to use this name for processing", logger.DEBUG) continue if not episodes: - returnStr += logHelper(u"Unable to find an episode number in the filename "+curName+", skipping", logger.DEBUG) + returnStr += logHelper(u"Unable to find an episode number in the filename "+curName+u", skipping", logger.DEBUG) continue # reverse-lookup the scene exceptions @@ -322,7 +325,7 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): sceneID = exceptionID break if sceneID: - returnStr += logHelper(u"Scene exception lookup got tvdb id "+str(sceneID)+", using that", logger.DEBUG) + returnStr += logHelper(u"Scene exception lookup got tvdb id "+str(sceneID)+u", using that", logger.DEBUG) break if sceneID: @@ -334,13 +337,13 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): # get the tvdb object from either the scene exception ID or the series name if tvdb_id: - returnStr += logHelper(u"Looking up ID "+str(tvdb_id)+" on TVDB", logger.DEBUG) + returnStr += logHelper(u"Looking up ID "+str(tvdb_id)+u" on TVDB", logger.DEBUG) showObj = t[tvdb_id] else: - returnStr += logHelper(u"Looking up name "+result.seriesname+" on TVDB", logger.DEBUG) + returnStr += logHelper(u"Looking up name "+result.seriesname+u" on TVDB", logger.DEBUG) showObj = t[result.seriesname] - returnStr += logHelper(u"Got tvdb_id "+str(showObj["id"])+" and series name "+showObj["seriesname"].decode('utf-8')+" from TVDB", logger.DEBUG) + returnStr += logHelper(u"Got tvdb_id "+str(showObj["id"])+u" and series name "+showObj["seriesname"].decode('utf-8')+u" from TVDB", logger.DEBUG) showInfo = (int(showObj["id"]), showObj["seriesname"]) @@ -369,12 +372,12 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): season = int(epObj["seasonnumber"]) episodes = [int(epObj["episodenumber"])] except tvdb_exceptions.tvdb_episodenotfound, e: - returnStr += logHelper(u"Unable to find episode with date "+str(episodes[0])+" for show "+showObj["seriesname"]+", skipping", logger.DEBUG) + returnStr += logHelper(u"Unable to find episode with date "+str(episodes[0])+u" for show "+showObj["seriesname"]+u", skipping", logger.DEBUG) continue # if we couldn't get the necessary info from either of the above methods, try the next name if tvdb_id == None or season == None or episodes == []: - returnStr += logHelper(u"Unable to get all the necessary info, ended up with tvdb_id "+str(tvdb_id)+", season "+str(season)+", and episodes "+str(episodes)+". Skipping to the next name...", logger.DEBUG) + returnStr += logHelper(u"Unable to get all the necessary info, ended up with tvdb_id "+str(tvdb_id)+u", season "+str(season)+u", and episodes "+str(episodes)+u". Skipping to the next name...", logger.DEBUG) continue # find the show in the showlist @@ -393,7 +396,7 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): if tvdb_id == None or season == None or episodes == []: # if we have a good enough result then fine, use it - returnStr += logHelper(u"Unable to figure out what this episode is, giving up. Ended up with tvdb_id "+str(tvdb_id)+", season "+str(season)+", and episodes "+str(episodes)+".", logger.DEBUG) + returnStr += logHelper(u"Unable to figure out what this episode is, giving up. Ended up with tvdb_id "+str(tvdb_id)+u", season "+str(season)+u", and episodes "+str(episodes)+u".", logger.DEBUG) return returnStr # if we found enough info but it wasn't a show we know about, give up @@ -402,7 +405,7 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): return returnStr # if we DO know about the show but its dir is offline, give up - if not os.path.isdir(showResults._location): + if not ek.ek(os.path.isdir, showResults._location): returnStr += logHelper(u"The show dir doesn't exist, canceling postprocessing", logger.DEBUG) return returnStr @@ -413,7 +416,7 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): newQuality = Quality.UNKNOWN for curName in finalNameList: curNewQuality = Quality.nameQuality(curName) - returnStr += logHelper(u"Looking up quality for name "+curName+", got "+Quality.qualityStrings[curNewQuality], logger.DEBUG) + returnStr += logHelper(u"Looking up quality for name "+curName+u", got "+Quality.qualityStrings[curNewQuality], logger.DEBUG) # just remember if we find a good quality if curNewQuality != Quality.UNKNOWN and newQuality == Quality.UNKNOWN: newQuality = curNewQuality @@ -424,7 +427,7 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): if newQuality != Quality.UNKNOWN: break newQuality = Quality.assumeQuality(curName) - returnStr += logHelper(u"Guessing quality for name "+curName+", got "+Quality.qualityStrings[curNewQuality], logger.DEBUG) + returnStr += logHelper(u"Guessing quality for name "+curName+u", got "+Quality.qualityStrings[curNewQuality], logger.DEBUG) if newQuality != Quality.UNKNOWN: break @@ -459,8 +462,8 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): curEp.status = Quality.compositeStatus(SNATCHED, newQuality) # figure out the new filename - biggestFileName = os.path.basename(fileName) - biggestFileExt = os.path.splitext(biggestFileName)[1] + biggestFileName = ek.ek(os.path.basename, fileName) + biggestFileExt = ek.ek(os.path.splitext, biggestFileName)[1] # if we're supposed to put it in a season folder then figure out what folder to use seasonFolder = '' @@ -469,7 +472,7 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): # search the show dir for season folders for curDir in os.listdir(rootEp.show.location): - if not os.path.isdir(os.path.join(rootEp.show.location, curDir)): + if not ek.ek(os.path.isdir, ek.ek(os.path.join, rootEp.show.location, curDir)): continue # if it's a season folder, check if it's the one we want @@ -486,18 +489,18 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): if rootEp.show.is_air_by_date: seasonFolder = str(rootEp.airdate.year) else: - seasonFolder = 'Season ' + str(rootEp.season) + seasonFolder = sickbeard.SEASON_FOLDERS_FORMAT % (rootEp.season) returnStr += logHelper(u"Season folders were " + str(rootEp.show.seasonfolders) + " which gave " + seasonFolder, logger.DEBUG) - destDir = os.path.join(rootEp.show.location, seasonFolder) + destDir = ek.ek(os.path.join, rootEp.show.location, seasonFolder) # movedFilePath is the full path to where we will move the file - movedFilePath = os.path.join(destDir, biggestFileName) + movedFilePath = ek.ek(os.path.join, destDir, biggestFileName) # renamedFilePath is the full path to the renamed file's eventual location if sickbeard.RENAME_EPISODES: - renamedFilePath = os.path.join(destDir, helpers.sanitizeFileName(rootEp.prettyName())+biggestFileExt) + renamedFilePath = ek.ek(os.path.join, destDir, helpers.sanitizeFileName(rootEp.prettyName())+biggestFileExt) else: renamedFilePath = movedFilePath returnStr += logHelper(u"The ultimate destination for " + fileName + " is " + renamedFilePath, logger.DEBUG) @@ -517,20 +520,20 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): # see if the existing file is bigger - if it is, bail (unless it's a proper or better quality in which case we're forcing an overwrite) if existingResult > 0: if rootEp.status in Quality.SNATCHED_PROPER: - returnStr += logHelper(u"There is already a file that's bigger at "+renamedFilePath+" but I'm going to overwrite it with a PROPER", logger.DEBUG) + returnStr += logHelper(u"There is already a file that's bigger at "+renamedFilePath+u" but I'm going to overwrite it with a PROPER", logger.DEBUG) elif oldStatus != None: - returnStr += logHelper(u"There is already a file that's bigger at "+renamedFilePath+" but I'm going to overwrite it because this one seems to have been downloaded on purpose", logger.DEBUG) + returnStr += logHelper(u"There is already a file that's bigger at "+renamedFilePath+u" but I'm going to overwrite it because this one seems to have been downloaded on purpose", logger.DEBUG) else: - returnStr += logHelper(u"There is already a file that's bigger at "+renamedFilePath+" - not processing this episode.", logger.DEBUG) + returnStr += logHelper(u"There is already a file that's bigger at "+renamedFilePath+u" - not processing this episode.", logger.DEBUG) # tag the dir so we know what happened if downloadDir: try: - oldDirName = os.path.abspath(downloadDir) - baseDirPath = os.path.dirname(oldDirName) - endDirPath = os.path.basename(oldDirName) + oldDirName = ek.ek(os.path.abspath, downloadDir) + baseDirPath = ek.ek(os.path.dirname, oldDirName) + endDirPath = ek.ek(os.path.basename, oldDirName) newDirPath = ek.ek(os.path.join, baseDirPath, '_UNDERSIZED_'+endDirPath) - returnStr += logHelper(u"Renaming the parent folder to indicate that the post process was failed: "+downloadDir+" -> "+newDirPath, logger.DEBUG) + returnStr += logHelper(u"Renaming the parent folder to indicate that the post process was failed: "+downloadDir+u" -> "+newDirPath, logger.DEBUG) os.rename(oldDirName, newDirPath) except (OSError, IOError), e: returnStr += logHelper(u"Failed renaming " + oldDirName + " to " + newDirPath + ": " + str(e), logger.ERROR) @@ -538,7 +541,7 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): return returnStr # if the dir doesn't exist (new season folder) then make it - if not os.path.isdir(destDir): + if not ek.ek(os.path.isdir, destDir): returnStr += logHelper(u"Season folder didn't exist, creating it", logger.DEBUG) os.mkdir(destDir) @@ -622,7 +625,7 @@ def processFile(fileName, downloadDir=None, nzbName=None, multi_file=False): for curScriptName in sickbeard.EXTRA_SCRIPTS: script_cmd = shlex.split(curScriptName) + [rootEp.location, biggestFileName, str(tvdb_id), str(season), str(episode), str(rootEp.airdate)] returnStr += logHelper(u"Executing command "+str(script_cmd)) - logger.log(u"Absolute path to script: "+os.path.abspath(script_cmd[0]), logger.DEBUG) + logger.log(u"Absolute path to script: "+ek.ek(os.path.abspath, script_cmd[0]), logger.DEBUG) try: p = subprocess.Popen(script_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR) out, err = p.communicate() diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py index e3c67a9342e9362c22cc4b6e87ea3cb46caa74d2..43316771f7f0b7dfebdcde04f3da002151d153f4 100644 --- a/sickbeard/providers/generic.py +++ b/sickbeard/providers/generic.py @@ -188,9 +188,21 @@ class GenericProvider: title = item.findtext('title') url = item.findtext('link').replace('&','&') + # parse the file name + try: + myParser = FileParser(title) + epInfo = myParser.parse() + except tvnamer_exceptions.InvalidFilename: + logger.log(u"Unable to parse the filename "+title+" into a valid episode", logger.WARNING) + continue + + if epInfo.seasonnumber != episode.season or episode.episode not in epInfo.episodenumbers: + logger.log("Episode "+title+" isn't "+str(episode.season)+"x"+str(episode.episode)+", skipping it", logger.DEBUG) + continue + quality = self.getQuality(item) - if not episode.show.wantEpisode(episode.season, episode.episode, quality, manualSearch): + if not episode.show.wantEpisode(epInfo.seasonnumber, epInfo.episodenumbers[0], quality, manualSearch): logger.log(u"Ignoring result "+title+" because we don't want an episode that is "+Quality.qualityStrings[quality], logger.DEBUG) continue diff --git a/sickbeard/providers/newzbin.py b/sickbeard/providers/newzbin.py index cc2102c9e74909944e3f6719ca96e237880ae808..a160e615333c5d824f0a0179b31de5ed38fbe6d0 100644 --- a/sickbeard/providers/newzbin.py +++ b/sickbeard/providers/newzbin.py @@ -56,9 +56,9 @@ class NewzbinDownloader(urllib.FancyURLopener): elif newzbinErrCode == 402: raise exceptions.AuthException("Newzbin account not premium status, can't download NZBs") - logger.log("Newzbin throttled our NZB downloading, pausing for " + result.group() + "seconds") + logger.log("Newzbin throttled our NZB downloading, pausing for " + result.group(1) + "seconds") - time.sleep(int(result.group())) + time.sleep(int(result.group(1))) raise exceptions.NewzbinAPIThrottled() diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py index 61d374c9440390ce286863c8ffe387693e4c11c3..c381514e33121f0a8a9bb476eb9d35934df76e36 100644 --- a/sickbeard/providers/newznab.py +++ b/sickbeard/providers/newznab.py @@ -126,6 +126,10 @@ class NewznabProvider(generic.NZBProvider): logger.log(u"Search url: " + searchURL, logger.DEBUG) data = self.getURL(searchURL) + + # hack this in until it's fixed server side + if not data.startswith('<?xml'): + data = '<?xml version="1.0" encoding="ISO-8859-1" ?>' + data if data == None: return [] @@ -213,6 +217,10 @@ class NewznabCache(tvcache.TVCache): data = self.provider.getURL(url) + # hack this in until it's fixed server side + if not data.startswith('<?xml'): + data = '<?xml version="1.0" encoding="ISO-8859-1" ?>' + data + return data def _checkAuth(self, data): diff --git a/sickbeard/queue.py b/sickbeard/queue.py index 8205501a7680f4c76a434674ff57c3592b594ba4..8fa878f3f4eb9bfa41e33f5efb6715a54098bdef 100644 --- a/sickbeard/queue.py +++ b/sickbeard/queue.py @@ -12,7 +12,7 @@ from sickbeard.tv import TVShow from sickbeard import exceptions from sickbeard import helpers from sickbeard import logger -from sickbeard import webserve +from sickbeard import ui class ShowQueue: @@ -234,15 +234,15 @@ class QueueItemAdd(QueueItem): except tvdb_exceptions.tvdb_exception, e: logger.log(u"Unable to add show due to an error with TVDB: "+str(e).decode('utf-8'), logger.ERROR) if self.show: - webserve.flash.error("Unable to add "+str(self.show.name)+" due to an error with TVDB") + ui.flash.error("Unable to add "+str(self.show.name)+" due to an error with TVDB") else: - webserve.flash.error("Unable to add show due to an error with TVDB") + ui.flash.error("Unable to add show due to an error with TVDB") self._finishEarly() return except exceptions.MultipleShowObjectsException: logger.log(u"The show in " + self.showDir + " is already in your show list, skipping", logger.ERROR) - webserve.flash.error("The show in " + self.showDir + " is already in your show list, skipping") + ui.flash.error("The show in " + self.showDir + " is already in your show list, skipping") self._finishEarly() return diff --git a/sickbeard/sceneHelpers.py b/sickbeard/sceneHelpers.py index ffa0e5231fbf9b37eba6caa5daae2d8ecf5b8d31..386b1c0747255bb4c521ce9b96e426666410e598 100644 --- a/sickbeard/sceneHelpers.py +++ b/sickbeard/sceneHelpers.py @@ -66,7 +66,12 @@ def sanitizeSceneName (name): def sceneToNormalShowNames(name): - return [name, name.replace(".and.", ".&.")] + results = [name] + + if '.and.' in name: + results.append(name.replace('.and.', '.&.')) + + return results def makeSceneShowSearchStrings(show): @@ -88,7 +93,7 @@ def makeSceneSeasonSearchString (show, segment, extraSearchType=None): else: numseasonsSQlResult = myDB.select("SELECT COUNT(DISTINCT season) as numseasons FROM tv_episodes WHERE showid = ? and season != 0", [show.tvdbid]) - numseasons = numseasonsSQlResult[0][0] + numseasons = int(numseasonsSQlResult[0][0]) seasonStrings = ["S%02d" % segment] # since nzbmatrix allows more than one search per request we search SxEE results too @@ -127,6 +132,10 @@ def makeSceneSeasonSearchString (show, segment, extraSearchType=None): def makeSceneSearchString (episode): + myDB = db.DBConnection() + numseasonsSQlResult = myDB.select("SELECT COUNT(DISTINCT season) as numseasons FROM tv_episodes WHERE showid = ? and season != 0", [episode.show.tvdbid]) + numseasons = int(numseasonsSQlResult[0][0]) + # see if we should use dates instead of episodes if episode.show.is_air_by_date and episode.airdate != datetime.date.fromordinal(1): epStrings = [str(episode.airdate)] @@ -134,6 +143,10 @@ def makeSceneSearchString (episode): epStrings = ["S%02iE%02i" % (int(episode.season), int(episode.episode)), "%ix%02i" % (int(episode.season), int(episode.episode))] + # for single-season shows just search for the show name + if numseasons == 1: + epStrings = [''] + showNames = set(makeSceneShowSearchStrings(episode.show)) toReturn = [] @@ -157,16 +170,20 @@ def allPossibleShowNames(show): newShowNames = [] + country_list = countryList + country_list.update(dict(zip(countryList.values(), countryList.keys()))) + # if we have "Show Name Australia" or "Show Name (Australia)" this will add "Show Name (AU)" for # any countries defined in common.countryList + # (and vice versa) for curName in showNames: - for curCountry in countryList: + for curCountry in country_list: if curName.endswith(' '+curCountry): - logger.log(u"Show names ends with "+curCountry+", so trying to add ("+countryList[curCountry]+") to it as well", logger.DEBUG) - newShowNames.append(curName.replace(' '+curCountry, ' ('+countryList[curCountry]+')')) + logger.log(u"Show names ends with "+curCountry+", so trying to add ("+country_list[curCountry]+") to it as well", logger.DEBUG) + newShowNames.append(curName.replace(' '+curCountry, ' ('+country_list[curCountry]+')')) elif curName.endswith(' ('+curCountry+')'): - logger.log(u"Show names ends with "+curCountry+", so trying to add ("+countryList[curCountry]+") to it as well", logger.DEBUG) - newShowNames.append(curName.replace(' ('+curCountry+')', ' ('+countryList[curCountry]+')')) + logger.log(u"Show names ends with "+curCountry+", so trying to add ("+country_list[curCountry]+") to it as well", logger.DEBUG) + newShowNames.append(curName.replace(' ('+curCountry+')', ' ('+country_list[curCountry]+')')) showNames += newShowNames diff --git a/sickbeard/searchCurrent.py b/sickbeard/searchCurrent.py index 3defa6512e0111465e732a72187ce626040fd143..c78902b62f82d5777eaf67e4fffa535bc6e1fd0b 100644 --- a/sickbeard/searchCurrent.py +++ b/sickbeard/searchCurrent.py @@ -38,14 +38,16 @@ class CurrentSearcher(): self.amActive = True - # pause the backlog to prevent race conditions downloading 2 episodes - logger.log(u"Pausing backlog so it doesn't collide with episode search", logger.DEBUG) - sickbeard.backlogSearchScheduler.action.amPaused = True - while sickbeard.backlogSearchScheduler.action.am_running(): - logger.log(u"Backlog isn't waiting yet, trying again in 1s", logger.DEBUG) - time.sleep(1) + backlogPaused = sickbeard.backlogSearchScheduler.action.amPaused + if not backlogPaused: + # pause the backlog to prevent race conditions downloading 2 episodes + logger.log(u"Pausing backlog so it doesn't collide with episode search", logger.DEBUG) + sickbeard.backlogSearchScheduler.action.amPaused = True + while sickbeard.backlogSearchScheduler.action.am_running(): + logger.log(u"Backlog isn't waiting yet, trying again in 1s", logger.DEBUG) + time.sleep(1) - logger.log(u"Backlog has stopped, running search now", logger.DEBUG) + logger.log(u"Backlog is stopped, running search now", logger.DEBUG) self._changeMissingEpisodes() @@ -71,8 +73,11 @@ class CurrentSearcher(): sickbeard.updateAiringList() sickbeard.updateComingList() - logger.log(u"Search is done, resuming backlog", logger.DEBUG) - sickbeard.backlogSearchScheduler.action.amPaused = False + if not backlogPaused: + logger.log(u"Search is done, resuming backlog", logger.DEBUG) + sickbeard.backlogSearchScheduler.action.amPaused = False + else: + logger.log(u"Search is done, leaving backlog paused", logger.DEBUG) self.amActive = False diff --git a/sickbeard/tv.py b/sickbeard/tv.py index 5a922ee93eebbb23f2ab532eb5ca926ffe114b14..6558d3854d7381a1833ae4c2d37dd44de7e57894 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -812,6 +812,7 @@ class TVShow(object): logger.log(u"any,best = "+str(anyQualities)+" "+str(bestQualities)+" and we are "+str(quality), logger.DEBUG) if quality not in anyQualities + bestQualities: + logger.log(u"I know for sure I don't want this episode, saying no", logger.DEBUG) return False myDB = db.DBConnection() @@ -838,6 +839,8 @@ class TVShow(object): elif manualSearch: logger.log(u"Usually I would ignore this ep but because you forced the search I'm overriding the default and allowing the quality", logger.DEBUG) return True + else: + logger.log(u"This quality looks like something we might want but I don't know for sure yet", logger.DEBUG) curStatus, curQuality = Quality.splitCompositeStatus(epStatus) diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py index de53cd04132b15759fe4dd184c42b3233cf039e1..562baba56f93a042229fa4f63cd73105af7accfa 100644 --- a/sickbeard/tvcache.py +++ b/sickbeard/tvcache.py @@ -217,6 +217,7 @@ class TVCache(): if sceneHelpers.isGoodResult(name, curShow, False): logger.log(u"Successfully matched "+name+" to "+curShow.name+" with regex", logger.DEBUG) tvdb_id = curShow.tvdbid + break if tvdb_id: diff --git a/sickbeard/tvrage.py b/sickbeard/tvrage.py index ba9e7a454cc81837f071cd0a93bb2720c0d1224e..fb7e4f33399ef83dee83f596053c3cc27c307c26 100644 --- a/sickbeard/tvrage.py +++ b/sickbeard/tvrage.py @@ -47,16 +47,21 @@ class TVRage: if self.show.tvrid == 0: # if it's the right show then use the tvrage ID that the last lookup found (cached in self._trvid) - if self.confirmShow() or self.checkSync(): + show_is_right = self.confirmShow() or self.checkSync() - if self._tvrid == 0 or self._tvrname == None: - raise exceptions.TVRageException("We confirmed sync but got invalid data (no ID/name)") + if not show_is_right: + raise exceptions.TVRageException("Shows aren't the same, aborting") + + if self._tvrid == 0 or self._tvrname == None: + raise exceptions.TVRageException("We confirmed sync but got invalid data (no ID/name)") + + if show_is_right: logger.log(u"Setting TVRage ID for "+show.name+" to "+str(self._tvrid)) self.show.tvrid = self._tvrid self.show.saveToDB() - if self.show.tvrname == "" or self.show.tvrname == None: + if not self.show.tvrname: if self._tvrname == None: self._getTVRageInfo() diff --git a/sickbeard/ui.py b/sickbeard/ui.py index 56768d969942c993a095dbeccbd1c7c2c7691925..2cb39305aa570520234ee43c5e7a0ca96bd4e18f 100644 --- a/sickbeard/ui.py +++ b/sickbeard/ui.py @@ -127,3 +127,26 @@ def addShowsFromRootDir(dir): del sickbeard.loadingShowList[showDir] return returnStr + +class Flash: + _messages = [] + _errors = [] + + def message(self, title, detail=''): + Flash._messages.append((title, detail)) + + def error(self, title, detail=''): + Flash._errors.append((title, detail)) + + def messages(self): + tempMessages = Flash._messages + Flash._messages = [] + return tempMessages + + def errors(self): + tempErrors = Flash._errors + Flash._errors = [] + return tempErrors + +flash = Flash() + diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index 9bdba4938adbd84f041428cfa91b6d2a0ba3b608..f7c304b3691212ced608bf31a0235dfd6ae322ab 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -77,7 +77,7 @@ class CheckVersion(): class UpdateManager(): def get_update_url(self): - return sickbeard.WEB_ROOT+"/home/update/?pid="+str(os.getpid()) + return sickbeard.WEB_ROOT+"/home/update/?pid="+str(sickbeard.PID) class WindowsUpdateManager(UpdateManager): diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 364262d772bc64e0171edbccf672d953423d0d2d..1a0ceb68383f85ed64c67b6df911bcbe0c210ad9 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -35,7 +35,7 @@ import metadata.helpers from sickbeard import config from sickbeard import history, notifiers, processTV, search, providers -from sickbeard import tv, versionChecker +from sickbeard import tv, versionChecker, ui from sickbeard import logger, helpers, exceptions, classes, db from sickbeard import encodingKludge as ek @@ -58,28 +58,6 @@ import sickbeard from sickbeard import browser -class Flash: - _messages = [] - _errors = [] - - def message(self, title, detail=''): - Flash._messages.append((title, detail)) - - def error(self, title, detail=''): - Flash._errors.append((title, detail)) - - def messages(self): - tempMessages = Flash._messages - Flash._messages = [] - return tempMessages - - def errors(self): - tempErrors = Flash._errors - Flash._errors = [] - return tempErrors - -flash = Flash() - class PageTemplate (Template): def __init__(self, *args, **KWs): KWs['file'] = os.path.join(sickbeard.PROG_DIR, "data/interfaces/default/", KWs['file']) @@ -99,7 +77,7 @@ class PageTemplate (Template): { 'title': 'Config', 'key': 'config' }, { 'title': logPageTitle, 'key': 'errorlogs' }, ] - self.flash = Flash() + self.flash = ui.Flash() def redirect(abspath, *args, **KWs): assert abspath[0] == '/' @@ -123,7 +101,7 @@ def _munge(string): def _genericMessage(subject, message): t = PageTemplate(file="genericMessage.tmpl") - t.submenu = HomeMenu + t.submenu = HomeMenu() t.subject = subject t.message = message return _munge(t) @@ -169,7 +147,7 @@ class ManageSearches: # force it to run the next time it looks sickbeard.backlogSearchScheduler.forceSearch() logger.log(u"Backlog search started in background") - flash.message('Backlog search started', + ui.flash.message('Backlog search started', 'The backlog search has begun and will run in the background') redirect("/manage/manageSearches") @@ -180,7 +158,7 @@ class ManageSearches: result = sickbeard.currentSearchScheduler.forceRun() if result: logger.log(u"Search forced") - flash.message('Episode search started', + ui.flash.message('Episode search started', 'Note: RSS feeds may not be updated if they have been retrieved too recently') redirect("/manage/manageSearches") @@ -325,7 +303,7 @@ class Manage: errors.append('<b>%s:</b><br />\n<ul>' % showObj.name + '\n'.join(['<li>%s</li>' % error for error in curErrors]) + "</ul>") if len(errors) > 0: - flash.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"), + ui.flash.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"), "<br />\n".join(errors)) redirect("/manage") @@ -388,7 +366,7 @@ class Manage: if len(errors) > 0: - flash.error("Errors encountered", + ui.flash.error("Errors encountered", '<br >\n'.join(errors)) messageDetail = "" @@ -409,7 +387,7 @@ class Manage: messageDetail += "</li>\n</ul>\n<br />" if len(updates+refreshes+renames) > 0: - flash.message("The following actions were queued:<br /><br />", + ui.flash.message("The following actions were queued:<br /><br />", messageDetail) redirect("/manage") @@ -440,7 +418,7 @@ class History: myDB = db.DBConnection() myDB.action("DELETE FROM history WHERE 1=1") - flash.message('History cleared') + ui.flash.message('History cleared') redirect("/history") @@ -449,7 +427,7 @@ class History: myDB = db.DBConnection() myDB.action("DELETE FROM history WHERE date < "+str((datetime.datetime.today()-datetime.timedelta(days=30)).strftime(history.dateFormat))) - flash.message('Removed all history entries greater than 30 days old') + ui.flash.message('Removed all history entries greater than 30 days old') redirect("/history") @@ -472,7 +450,7 @@ class ConfigGeneral: @cherrypy.expose def saveGeneral(self, log_dir=None, web_port=None, web_log=None, web_ipv6=None, launch_browser=None, web_username=None, - web_password=None, season_folders_default=None, + web_password=None, season_folders_format=None, season_folders_default=None, version_notify=None, naming_show_name=None, naming_ep_type=None, naming_multi_ep_type=None, naming_ep_name=None, naming_use_periods=None, naming_sep_type=None, naming_quality=None, @@ -586,6 +564,7 @@ class ConfigGeneral: sickbeard.ART_THUMBNAILS = art_thumbnails sickbeard.ART_SEASON_THUMBNAILS = art_season_thumbnails + sickbeard.SEASON_FOLDERS_FORMAT = season_folders_format sickbeard.SEASON_FOLDERS_DEFAULT = int(season_folders_default) sickbeard.QUALITY_DEFAULT = newQuality @@ -611,10 +590,10 @@ class ConfigGeneral: if len(results) > 0: for x in results: logger.log(x, logger.ERROR) - flash.error('Error(s) Saving Configuration', + ui.flash.error('Error(s) Saving Configuration', '<br />\n'.join(results)) else: - flash.message('Configuration Saved') + ui.flash.message('Configuration Saved') redirect("/config/general/") @@ -793,10 +772,10 @@ class ConfigEpisodeDownloads: if len(results) > 0: for x in results: logger.log(x, logger.ERROR) - flash.error('Error(s) Saving Configuration', + ui.flash.error('Error(s) Saving Configuration', '<br />\n'.join(results)) else: - flash.message('Configuration Saved') + ui.flash.message('Configuration Saved') redirect("/config/episodedownloads/") @@ -966,10 +945,10 @@ class ConfigProviders: if len(results) > 0: for x in results: logger.log(x, logger.ERROR) - flash.error('Error(s) Saving Configuration', + ui.flash.error('Error(s) Saving Configuration', '<br />\n'.join(results)) else: - flash.message('Configuration Saved') + ui.flash.message('Configuration Saved') redirect("/config/providers/") @@ -1037,10 +1016,10 @@ class ConfigNotifications: if len(results) > 0: for x in results: logger.log(x, logger.ERROR) - flash.error('Error(s) Saving Configuration', + ui.flash.error('Error(s) Saving Configuration', '<br />\n'.join(results)) else: - flash.message('Configuration Saved') + ui.flash.message('Configuration Saved') redirect("/config/notifications/") @@ -1065,13 +1044,14 @@ class Config: def haveXBMC(): return sickbeard.XBMC_HOST != None and len(sickbeard.XBMC_HOST) > 0 -HomeMenu = [ +def HomeMenu(): + return [ { 'title': 'Add Shows', 'path': 'home/addShows/' }, { 'title': 'Manual Post-Processing', 'path': 'home/postprocess/' }, { 'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': haveXBMC }, - { 'title': 'Restart', 'path': 'home/restart/?pid='+str(os.getpid()) }, + { 'title': 'Restart', 'path': 'home/restart/?pid='+str(sickbeard.PID) }, { 'title': 'Shutdown', 'path': 'home/shutdown/' }, -] + ] class HomePostProcess: @@ -1079,7 +1059,7 @@ class HomePostProcess: def index(self): t = PageTemplate(file="home_postprocess.tmpl") - t.submenu = HomeMenu + t.submenu = HomeMenu() return _munge(t) @cherrypy.expose @@ -1102,7 +1082,7 @@ class NewHomeAddShows: def index(self): t = PageTemplate(file="home_addShows.tmpl") - t.submenu = HomeMenu + t.submenu = HomeMenu() return _munge(t) @cherrypy.expose @@ -1139,7 +1119,7 @@ class NewHomeAddShows: if not os.path.isdir(dir): logger.log(u"The provided directory "+dir+" doesn't exist", logger.ERROR) - flash.error("Unable to find the directory <tt>%s</tt>" % dir) + ui.flash.error("Unable to find the directory <tt>%s</tt>" % dir) redirect("/home/addShows") showDirs = [] @@ -1152,14 +1132,14 @@ class NewHomeAddShows: if len(showDirs) == 0: logger.log(u"The provided directory "+dir+" has no shows in it", logger.ERROR) - flash.error("The provided root folder <tt>%s</tt> has no shows in it." % dir) + ui.flash.error("The provided root folder <tt>%s</tt> has no shows in it." % dir) redirect("/home/addShows") #result = ui.addShowsFromRootDir(dir) myTemplate = PageTemplate(file="home_addRootDir.tmpl") myTemplate.showDirs = [urllib.quote_plus(x.encode('utf-8')) for x in showDirs] - myTemplate.submenu = HomeMenu + myTemplate.submenu = HomeMenu() return _munge(myTemplate) url = "/home/addShows/addShow?"+"&".join(["showDir="+urllib.quote_plus(x.encode('utf-8')) for x in showDirs]) @@ -1178,7 +1158,7 @@ class NewHomeAddShows: # if we got a TVDB ID then make a show out of it sickbeard.showQueueScheduler.action.addShow(int(whichSeries), showToAdd) - flash.message('Show added', 'Adding the specified show into '+showToAdd) + ui.flash.message('Show added', 'Adding the specified show into '+showToAdd) # no need to display anything now that we added the show, so continue on to the next show return self.addShows(showDirs) @@ -1196,7 +1176,7 @@ class NewHomeAddShows: redirect("/home") t = PageTemplate(file="home_addShow.tmpl") - t.submenu = HomeMenu + t.submenu = HomeMenu() # make sure everything's unescaped showDirs = [os.path.normpath(urllib.unquote_plus(x)) for x in showDirs] @@ -1207,7 +1187,7 @@ class NewHomeAddShows: # if the dir we're given doesn't exist and we can't create it then skip it if not helpers.makeDir(showToAdd): - flash.error("Warning", "Unable to create dir "+showToAdd+", skipping") + ui.flash.error("Warning", "Unable to create dir "+showToAdd+", skipping") # recursively continue on our way, encoding the input as though we came from the web form return self.addShows([urllib.quote_plus(x.encode('utf-8')) for x in restOfShowDirs]) @@ -1219,18 +1199,18 @@ class NewHomeAddShows: except exceptions.NoNFOException, e: # we couldn't get a tvdb id from the file so let them know and just print the search page if ek.ek(os.path.isfile, ek.ek(os.path.join, showToAdd, "tvshow.nfo.old")): - flash.error('Warning', 'Unable to retrieve TVDB ID from tvshow.nfo, renamed it to tvshow.nfo.old and ignoring it') + ui.flash.error('Warning', 'Unable to retrieve TVDB ID from tvshow.nfo, renamed it to tvshow.nfo.old and ignoring it') # no tvshow.nfo.old means we couldn't rename it and we can't continue adding this show # encode the input as though we came from the web form else: - flash.error('Warning', 'Unable to retrieve TVDB ID from tvshow.nfo and unable to rename it - you will need to remove it manually') + ui.flash.error('Warning', 'Unable to retrieve TVDB ID from tvshow.nfo and unable to rename it - you will need to remove it manually') return self.addShows([urllib.quote_plus(x.encode('utf-8')) for x in restOfShowDirs]) # if we got a TVDB ID then make a show out of it if tvdb_id: sickbeard.showQueueScheduler.action.addShow(tvdb_id, showToAdd) - flash.message('Show added', 'Auto-added show from tvshow.nfo in '+showToAdd) + ui.flash.message('Show added', 'Auto-added show from tvshow.nfo in '+showToAdd) # no need to display anything now that we added the show, so continue on to the next show return self.addShows([urllib.quote_plus(x.encode('utf-8')) for x in restOfShowDirs]) @@ -1329,7 +1309,7 @@ class Home: def index(self): t = PageTemplate(file="home.tmpl") - t.submenu = HomeMenu + t.submenu = HomeMenu() return _munge(t) addShows = NewHomeAddShows() @@ -1380,7 +1360,7 @@ class Home: @cherrypy.expose def restart(self, pid=None): - if str(pid) != str(os.getpid()): + if str(pid) != str(sickbeard.PID): redirect("/home") # do a soft restart @@ -1394,7 +1374,7 @@ class Home: @cherrypy.expose def update(self, pid=None): - if str(pid) != str(os.getpid()): + if str(pid) != str(sickbeard.PID): redirect("/home") updated = sickbeard.versionCheckScheduler.action.update() @@ -1434,19 +1414,19 @@ class Home: t.showLoc = (showObj._location, False) if sickbeard.showQueueScheduler.action.isBeingAdded(showObj): - flash.message('This show is in the process of being downloaded from theTVDB.com - the info below is incomplete.') + ui.flash.message('This show is in the process of being downloaded from theTVDB.com - the info below is incomplete.') elif sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): - flash.message('The information below is in the process of being updated.') + ui.flash.message('The information below is in the process of being updated.') elif sickbeard.showQueueScheduler.action.isBeingRefreshed(showObj): - flash.message('The episodes below are currently being refreshed from disk') + ui.flash.message('The episodes below are currently being refreshed from disk') elif sickbeard.showQueueScheduler.action.isInRefreshQueue(showObj): - flash.message('This show is queued to be refreshed.') + ui.flash.message('This show is queued to be refreshed.') elif sickbeard.showQueueScheduler.action.isInUpdateQueue(showObj): - flash.message('This show is queued and awaiting an update.') + ui.flash.message('This show is queued and awaiting an update.') if not sickbeard.showQueueScheduler.action.isBeingAdded(showObj): if not sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): @@ -1506,7 +1486,7 @@ class Home: if not location and not anyQualities and not bestQualities and not seasonfolders: t = PageTemplate(file="editShow.tmpl") - t.submenu = HomeMenu + t.submenu = HomeMenu() with showObj.lock: t.show = showObj @@ -1574,7 +1554,7 @@ class Home: return errors if len(errors) > 0: - flash.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"), + ui.flash.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"), '<ul>' + '\n'.join(['<li>%s</li>' % error for error in errors]) + "</ul>") redirect("/home/displayShow?show=" + show) @@ -1596,7 +1576,7 @@ class Home: showObj.deleteShow() - flash.message('<b>%s</b> has been deleted' % showObj.name) + ui.flash.message('<b>%s</b> has been deleted' % showObj.name) redirect("/home") @cherrypy.expose @@ -1614,7 +1594,7 @@ class Home: try: sickbeard.showQueueScheduler.action.refreshShow(showObj) except exceptions.CantRefreshException, e: - flash.error("Unable to refresh this show.", + ui.flash.error("Unable to refresh this show.", str(e)) time.sleep(3) @@ -1636,7 +1616,7 @@ class Home: try: sickbeard.showQueueScheduler.action.updateShow(showObj, bool(force)) except exceptions.CantUpdateException, e: - flash.error("Unable to update this show.", + ui.flash.error("Unable to update this show.", str(e)) # just give it some time @@ -1650,9 +1630,9 @@ class Home: for curHost in [x.strip() for x in sickbeard.XBMC_HOST.split(",")]: if xbmc.updateLibrary(curHost, showName=showName): - flash.message("Command sent to XBMC host " + curHost + " to update library") + ui.flash.message("Command sent to XBMC host " + curHost + " to update library") else: - flash.error("Unable to contact XBMC host " + curHost) + ui.flash.error("Unable to contact XBMC host " + curHost) redirect('/home') @@ -1680,7 +1660,7 @@ class Home: if show == None or eps == None or status == None: errMsg = "You must specify a show and at least one episode" if direct: - flash.error('Error', errMsg) + ui.flash.error('Error', errMsg) return json.dumps({'result': 'error'}) else: return _genericMessage("Error", errMsg) @@ -1688,7 +1668,7 @@ class Home: if not statusStrings.has_key(int(status)): errMsg = "Invalid status" if direct: - flash.error('Error', errMsg) + ui.flash.error('Error', errMsg) return json.dumps({'result': 'error'}) else: return _genericMessage("Error", errMsg) @@ -1698,7 +1678,7 @@ class Home: if showObj == None: errMsg = "Error", "Show not in show list" if direct: - flash.error('Error', errMsg) + ui.flash.error('Error', errMsg) return json.dumps({'result': 'error'}) else: return _genericMessage("Error", errMsg) @@ -1750,7 +1730,7 @@ class Home: if not foundEpisode: message = 'No downloads were found' - flash.error(message, "Couldn't find a download for <i>%s</i>" % epObj.prettyName(True)) + ui.flash.error(message, "Couldn't find a download for <i>%s</i>" % epObj.prettyName(True)) logger.log(message) else: @@ -1760,9 +1740,9 @@ class Home: result = search.snatchEpisode(foundEpisode) providerModule = foundEpisode.provider if providerModule == None: - flash.error('Provider is configured incorrectly, unable to download') + ui.flash.error('Provider is configured incorrectly, unable to download') else: - flash.message('Episode <b>%s</b> snatched from <b>%s</b>' % (foundEpisode.name, providerModule.name)) + ui.flash.message('Episode <b>%s</b> snatched from <b>%s</b>' % (foundEpisode.name, providerModule.name)) #TODO: check if the download was successful @@ -1792,7 +1772,7 @@ class WebInterface: if showObj == None: return "Unable to find show" #TODO: make it return a standard image - posterFilename = os.path.abspath(ek.ek(os.path.join, showObj.location, "folder.jpg")) + posterFilename = os.path.abspath(sickbeard.metadata_generator.get_poster_path(showObj)) if ek.ek(os.path.isfile, posterFilename): try: from PIL import Image