Merge branch 'release/0.5.0'

This commit is contained in:
adam 2014-12-21 19:40:47 +08:00
commit 72e94116b7
68 changed files with 2818 additions and 3016 deletions

View file

@ -1,3 +1,45 @@
### 0.5.0 (2014-12-21 11:40:00 UTC)
* Fix searches freezing due to unescaped ignored or required words
* Add failed database to unit tests tear down function
* Fix purging of database files in tear down function during unit tests
* Add ability to auto focus Search Show box on Home page and control this option via General Config/Interface
* Change some provider images. Add a few new images
* Remove redundant Coming Eps template code used in the old UI
* Change update Plex notifier (port from SickBeard)
* Change Plex notifications to allow authenticated library updates (port from mmccurdy07/Sick-Beard)
* Change Config/Notifications/Plex logo and description (adapted port from mmccurdy07/Sick-Beard)
* Add ability for CSS/JS to target a specific page and layout
* Remove legacy sickbeard updater and build automation code
* Fix multiple instances of SG being able to start
* Fix garbled text appearing during startup in console
* Fix startup code order and general re-factoring (adapted from midgetspy/Sick-Beard)
* Add database migration code
* Change KickassTorrents provider URLs
* Fix missing Content-Type headers for posters and banners
* Remove config Backup & Restore
* Fix article removal for sorting on Display Show, and API pages
* Fix visual positioning of sprites on Config page
* Fix missing navbar gradients for all browsers
* Update qTip2 to v2.2.1
* Overhaul all Add Show pages
* Fix Display Show next/previous when show list is split
* Change Display Show next/previous when show list is not split to loop around
* Fix SQL statements that have dynamic table names to use proper syntax
* Fix port checking code preventing startup directly after a SG restart
* Add a link from the footer number of snatched to episode snatched overview page. The link to the
Episode Overview page is available on all pages except on the Episode Overview page
* Change the default state for all check boxes on the Episode Overview page to not checked
* Add validation to Go button to ensure at least one item is checked on Episode Overview page
* Add highlight to current status text in header on Episode Overview page
* Fix table alignment on homepage
* Fix duplicate entries in cache database
* Fix network sorting on home page
* Fix restart issue
* Fix to use new TorrentDay URLs
* Fix typo in menu item Manage/Update XBMC
### 0.4.0 (2014-12-04 10:50:00 UTC)
* Change footer stats to not add newlines when copy/pasting from them

View file

@ -226,13 +226,13 @@ class SickGear(object):
if self.runAsDaemon:
pid_dir = os.path.dirname(self.PIDFILE)
if not os.access(pid_dir, os.F_OK):
sys.exit("PID dir: " + pid_dir + " doesn't exist. Exiting.")
sys.exit(u"PID dir: %s doesn't exist. Exiting." % pid_dir)
if not os.access(pid_dir, os.W_OK):
sys.exit("PID dir: " + pid_dir + " must be writable (write permissions). Exiting.")
sys.exit(u'PID dir: %s must be writable (write permissions). Exiting.' % pid_dir)
else:
if self.consoleLogging:
sys.stdout.write("Not running in daemon mode. PID file creation disabled.\n")
print u'Not running in daemon mode. PID file creation disabled'
self.CREATEPID = False
@ -244,34 +244,28 @@ class SickGear(object):
if not os.access(sickbeard.DATA_DIR, os.F_OK):
try:
os.makedirs(sickbeard.DATA_DIR, 0744)
except os.error, e:
raise SystemExit("Unable to create datadir '" + sickbeard.DATA_DIR + "'")
except os.error:
sys.exit(u'Unable to create data directory: %s + Exiting.' % sickbeard.DATA_DIR)
# Make sure we can write to the data dir
if not os.access(sickbeard.DATA_DIR, os.W_OK):
raise SystemExit("Datadir must be writeable '" + sickbeard.DATA_DIR + "'")
sys.exit(u'Data directory: %s must be writable (write permissions). Exiting.' % sickbeard.DATA_DIR)
# Make sure we can write to the config file
if not os.access(sickbeard.CONFIG_FILE, os.W_OK):
if os.path.isfile(sickbeard.CONFIG_FILE):
raise SystemExit("Config file '" + sickbeard.CONFIG_FILE + "' must be writeable.")
sys.exit(u'Config file: %s must be writeable (write permissions). Exiting.' % sickbeard.CONFIG_FILE)
elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK):
raise SystemExit(
"Config file root dir '" + os.path.dirname(sickbeard.CONFIG_FILE) + "' must be writeable.")
# Check if we need to perform a restore first
restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore')
if os.path.exists(restoreDir):
if self.restore(restoreDir, sickbeard.DATA_DIR):
logger.log(u"Restore successful...")
else:
logger.log(u"Restore FAILED!", logger.ERROR)
sys.exit(u'Config file directory: %s must be writeable (write permissions). Exiting'
% os.path.dirname(sickbeard.CONFIG_FILE))
os.chdir(sickbeard.DATA_DIR)
if self.consoleLogging:
print u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE
# Load the config and publish it to the sickbeard package
if not os.path.isfile(sickbeard.CONFIG_FILE):
logger.log(u"Unable to find '" + sickbeard.CONFIG_FILE + "' , all settings will be default!", logger.ERROR)
print u'Unable to find "%s", all settings will be default!' % sickbeard.CONFIG_FILE
sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
@ -279,15 +273,13 @@ class SickGear(object):
if CUR_DB_VERSION > 0:
if CUR_DB_VERSION < MIN_DB_VERSION:
raise SystemExit("Your database version (" + str(
CUR_DB_VERSION) + ") is too old to migrate from with this version of SickGear (" + str(
MIN_DB_VERSION) + ").\n" + \
"Upgrade using a previous version of SB first, or start with no database file to begin fresh.")
print u'Your database version (%s) is too old to migrate from with this version of SickGear' \
% CUR_DB_VERSION
sys.exit(u'Upgrade using a previous version of SG first, or start with no database file to begin fresh')
if CUR_DB_VERSION > MAX_DB_VERSION:
raise SystemExit("Your database version (" + str(
CUR_DB_VERSION) + ") has been incremented past what this version of SickGear supports (" + str(
MAX_DB_VERSION) + ").\n" + \
"If you have used other forks of SB, your database may be unusable due to their modifications.")
print u'Your database version (%s) has been incremented past what this version of SickGear supports' \
% CUR_DB_VERSION
sys.exit(u'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=self.consoleLogging)
@ -298,9 +290,6 @@ class SickGear(object):
# Get PID
sickbeard.PID = os.getpid()
# Build from the DB to start with
self.loadShowsFromDB()
if self.forcedPort:
logger.log(u"Forcing web server to port " + str(self.forcedPort))
self.startPort = self.forcedPort
@ -339,9 +328,12 @@ class SickGear(object):
# start web server
try:
# used to check if existing SG instances have been started
sickbeard.helpers.wait_for_free_port(self.web_options['host'], self.web_options['port'])
self.webserver = WebServer(self.web_options)
self.webserver.start()
except IOError:
except Exception:
logger.log(u"Unable to start web server, is something else running on port %d?" % self.startPort,
logger.ERROR)
if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon:
@ -349,8 +341,16 @@ class SickGear(object):
sickbeard.launchBrowser(self.startPort)
os._exit(1)
if self.consoleLogging:
print "Starting up SickGear " + sickbeard.BRANCH + " from " + sickbeard.CONFIG_FILE
# Check if we need to perform a restore first
restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore')
if os.path.exists(restoreDir):
if self.restore(restoreDir, sickbeard.DATA_DIR):
logger.log(u"Restore successful...")
else:
logger.log_error_and_exit(u"Restore FAILED!", logger.ERROR)
# Build from the DB to start with
self.loadShowsFromDB()
# Fire up all our threads
sickbeard.start()
@ -503,17 +503,6 @@ class SickGear(object):
if install_type in ('git', 'source'):
popen_list = [sys.executable, sickbeard.MY_FULLNAME]
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(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(sickbeard.PID),
sys.executable,
sickbeard.MY_FULLNAME]
if popen_list:
popen_list += sickbeard.MY_ARGS

View file

@ -1,24 +0,0 @@
2014-10-02
-Move the sql from the *.tmpl files
-Add anidb indexer
-Add supplemental data from anidb during postprocessing
-Fix grabbing non-propers as propers for certain air-by-date shows
-Add prefer torrents/usenet and allow custom delay between grabs from torrents/usenet
-Better postprocessing handling for torrents
2014-10-07
-Add release groups/require words/ignore words to add show page
-Add 'add show and add another show' button to add show page
-Change the hardcoded global ignore words to optional
2014-10-08
-Add login page for http auth as opposed to browser dialog box
2014-10-13
-Fix broken backlog
-Fix season searches
2014-10-21
-Remove qtip from config providers and go back to tab
-Find out why superfish & supersubs js breaks provider sorting
-Ability to save poster sorting and asc/desc in config

View file

@ -171,11 +171,6 @@ inc_top.tmpl
background: url("../images/ico/favicon-16x16.png") 0 0 no-repeat;
}
[class^="icon16-"],
[class*=" icon16-"] {
background-image: url("../images/glyphicons-config.png");
}
.ui-autocomplete-loading {
background: white url("../images/loading16.gif") right center no-repeat;
}
@ -538,10 +533,6 @@ home.tmpl
margin-right: 5px;
}
.search {
margin-bottom: 10px;
}
.ui-progressbar {
height: 20px;
line-height: 18px;
@ -782,122 +773,38 @@ td.tvShow a:hover {
/* =======================================================================
home_addShows.tmpl
========================================================================== */
#addShowPortal {
width: 700px;
padding: 10px 0;
margin-right: auto;
margin-left: auto;
}
#addShowPortal a {
padding: 10px;
}
div.button {
display: table-cell;
vertical-align: middle;
padding-left: 10px;
}
div.buttontext {
display: table-cell;
padding-left: 20px;
text-align: left;
white-space: normal;
}
div.buttontext h3 {
margin-top: 10px;
}
div.buttontext p {
font-size: 13px;
}
.icon-addnewshow {
background-image: url("../images/addshows/add-new32-white.png");
width: 32px;
height: 32px;
}
.icon-addtrendingshow {
background-image: url("../images/addshows/add-trending32-white.png");
width: 32px;
height: 32px;
}
.icon-addrecommendedshow {
background-image: url("../images/addshows/add-trakt32-white.png");
width: 32px;
height: 32px;
}
.icon-addexistingshow {
background-image: url("../images/addshows/add-existing32-white.png");
width: 32px;
height: 32px;
}
/* =======================================================================
home_newShow.tmpl
========================================================================== */
#addShowForm, #recommendedShowsForm {
margin-left: auto;
margin-right: auto;
}
#newShowPortal {
width: 800px;
padding: 10px 0;
margin-right: auto;
margin-left: auto;
}
#displayText {
padding: 8px;
overflow: hidden;
font-size: 14px;
background-color: #3d3d3d;
border: 1px solid #111;
}
#searchResults input[type="radio"] {
vertical-align: -2px;
background-color: rgb(17, 120, 179);
border: 0;
}
/* =======================================================================
home_addExistingShow.tmpl
========================================================================== */
.existingtabs {
padding: 1em 1.4em;
}
ul#rootDirStaticList {
width: 90%;
margin-right: auto;
margin-left: auto;
text-align: left;
}
ul#rootDirStaticList li {
padding: 4px 5px 4px 5px;
margin: 2px;
list-style: none outside none;
cursor: pointer;
background: #3d3d3d;
}
ul#rootDirStaticList li label {
margin-top: 5px;
margin-bottom: 5px;
}
ul#rootDirStaticList li input[type="checkbox"] {
vertical-align: -2px;
}
/* =======================================================================
home_trendingShows.tmpl
========================================================================== */
@ -1541,32 +1448,6 @@ select .selected {
border-top: 1px dotted #666666;
}
[class^="icon16-"], [class*=" icon16-"] {
background-image: url("../images/glyphicons-config.png");
background-repeat: no-repeat;
display: inline-block;
height: 16px;
vertical-align: text-top;
width: 16px;
margin-top: 1px;
}
.icon16-github {
background-position: 0 0;
}
.icon16-mirc {
background-position: -26px 0;
}
.icon16-sg {
background-position: -52px 0;
}
.icon16-web {
background-position: -78px 0;
}
.icon16-win {
background-position: -104px 0;
}
/* =======================================================================
config_postProcessing.tmpl
========================================================================== */
@ -1869,11 +1750,6 @@ body {
background-color: #222;
}
html * {
outline: 0 !important;
}
input[type="radio"] {
margin: 2px 0px 0px;
line-height: normal;
@ -1897,8 +1773,12 @@ input, textarea, select, .uneditable-input {
.navbar-default {
background-color: #15528F;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#297AB8', endColorstr='#15528F');
background: -webkit-gradient(linear, left top, left bottom, from(#297AB8), to(#15528F));
background: -moz-linear-gradient(top, #297AB8, #15528F);
background-image: -ms-linear-gradient(top, #297AB8 0%, #15528F 100%);
background-image: -moz-linear-gradient(top, #297AB8 0%, #15528F 100%);
background-image: -o-linear-gradient(top, #297AB8 0%, #15528F 100%);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #297AB8), color-stop(1, #15528F));
background-image: -webkit-linear-gradient(top, #297AB8 0%, #15528F 100%);
background-image: linear-gradient(to bottom, #297AB8 0%, #15528F 100%);
border-color: #3e3f3a;
}
@ -2033,12 +1913,6 @@ fieldset[disabled] .navbar-default .btn-link:focus {
color: #000000;
}
.form-control-inline {
min-width: 0;
width: auto;
display: inline;
}
.btn {
color: #fff;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75);
@ -2083,7 +1957,6 @@ fieldset[disabled] .navbar-default .btn-link:focus {
.btn:hover {
color: #fff;
text-decoration: none;
background-color: #2672B6;
*background-color: #2672B6;
background-position: 0 -150px;
@ -2116,8 +1989,7 @@ fieldset[disabled] .navbar-default .btn-link:focus {
.btn.disabled,
.btn[disabled] {
cursor: default;
background-color: #15528F;
background-image: none;
background: #15528F none;
opacity: 0.65;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
@ -2125,19 +1997,6 @@ fieldset[disabled] .navbar-default .btn-link:focus {
box-shadow: none;
}
.btn-large {
padding: 9px 14px;
font-size: 15px;
line-height: normal;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.btn-large [class^="icon-"] {
margin-top: 1px;
}
.btn-small {
padding: 5px 9px;
font-size: 11px;
@ -2397,60 +2256,16 @@ pre {
/* =======================================================================
input sizing (for config pages)
========================================================================== */
#pickShow optgroup,
#editAProvider optgroup {
color: #eee;
background-color: rgb(51, 51, 51);
background-color: rgb(51, 51, 51);
}
#pickShow optgroup option,
#editAProvider optgroup option {
color: #222;
background-color: #fff;
}
#config select {
min-width: 0;
width: auto;
display: inline;
margin-top: -4px;
}
.btn-inline {
margin-top: -3px;
}
.input75 {
width: 75px;
margin-top: -4px;
}
.input100 {
width: 100px;
margin-top: -4px;
}
.input150 {
width: 150px;
margin-top: -4px;
}
.input200 {
width: 200px;
margin-top: -4px;
}
.input250 {
width: 250px;
margin-top: -4px;
}
.input300 {
width: 300px;
margin-top: -4px;
}
.input350 {
width: 350px;
margin-top: -4px;
background-color: #fff;
}
/* =======================================================================
@ -2529,92 +2344,31 @@ browser.css
/* =======================================================================
formWizard.css
formWizard
========================================================================== */
fieldset.sectionwrap {
width: 800px;
padding: 5px;
text-align: left;
border-width: 0;
}
.step,
legend.legendStep {
color: #ffffff;
margin-bottom: 0px;
}
div.stepsguide {
margin-bottom: 15px;
overflow: hidden;
text-align: left;
cursor: pointer;
}
div.stepsguide .step {
float: left;
width: 266px;
font: bold 24px Arial;
}
div.stepsguide .step p {
margin: 12px 0;
border-bottom: 4px solid #23AFDC;
border-color: #23AFDC;
}
div.stepsguide .disabledstep {
color: #c4c4c4;
.disabledstep {
color: #646464;
}
div.stepsguide .disabledstep p {
border-bottom: 4px solid #1178B3;
}
div.stepsguide .step .smalltext {
font-size: 13px;
font-weight: normal;
}
div.formpaginate {
width: 800px;
margin-top: 1em;
overflow: auto;
font-weight: bold;
text-align: center;
border-color: #1178B3;
}
div.formpaginate .prev, div.formpaginate .next {
padding: 3px 6px;
color: #fff;
cursor: hand;
cursor: pointer;
background: #2265A1;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
.stepDiv {
padding: 15px;
}
/* step 3 related */
#customQuality {
display: block;
padding: 10px 0;
overflow: hidden;
clear: both;
}
#customQualityWrapper div.component-group-desc {
float: left;
width: 165px;
}
#customQualityWrapper div.component-group-desc p {
width: 85%;
margin: .8em 0;
font-size: 1.2em;
color: #666;
}
@ -2660,10 +2414,7 @@ tablesorter.css
.tablesorter th,
.tablesorter td {
padding: 4px;
border-top: #222 1px solid;
border-left: #222 1px solid;
vertical-align: middle;
border-color: #222;
}
/* remove extra border from left edge */
@ -2674,31 +2425,22 @@ tablesorter.css
.tablesorter th {
color: #fff;
text-align: center;
text-shadow: -1px -1px 0 rgba(0,0,0,0.3);
background-color: #15528F;
border-collapse: collapse;
font-weight: normal;
}
.tablesorter .tablesorter-header {
padding: 4px 18px 4px 18px;
cursor: pointer;
background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAAP///////yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==);
background-position: center right;
background-repeat: no-repeat;
background: #15528F url(data:image/gif;base64,R0lGODlhFQAJAIAAAP///////yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==) no-repeat center right;
/* background-image: url(../images/tablesorter/bg.gif); */
}
.tablesorter thead .tablesorter-headerDesc {
background-color: #297AB8;
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7);
background: #297AB8 url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7) no-repeat center right;
/* background-image: url(../images/tablesorter/asc.gif); */
}
.tablesorter thead .tablesorter-headerAsc {
background-color: #297AB8;
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7);
background: #297AB8 url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7) no-repeat center right;
/* background-image: url(../images/tablesorter/desc.gif); */
}
@ -2761,10 +2503,9 @@ thead.tablesorter-stickyHeader {
.tablesorter tfoot a {
color:#fff;
text-decoration: none;
}
#showListTable tbody {
#showListTable tbody {
color: #000;
}
@ -2903,6 +2644,25 @@ span.token-input-delete-token {
margin: 0 1px;
}
.step-one #searchResults .alt {
background-color: rgb(40, 40, 40);
}
#addRootDirTable td label .filepath,
.grey-text {
color:#999
}
#newShowPortal #displayText .show-name,
#newShowPortal #displayText .show-dest,
#newShowPortal #displayText p {
color: rgb(200, 200, 200)
}
#newShowPortal #displayText .show-name,
#newShowPortal #displayText .show-dest {
color: rgb(255, 255, 255)
}
/* =======================================================================
jquery.confirm.css
========================================================================== */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -167,12 +167,7 @@ inc_top.tmpl
background-image: url("../images/menu/menu-icons-white.png");
}
[class^="icon16-"],
[class*=" icon16-"] {
background-image: url("../images/glyphicons-config.png");
}
.ui-autocomplete-loading {
.ui-autocomplete-loading {
background: white url("../images/loading16.gif") right center no-repeat;
}
@ -525,10 +520,6 @@ home.tmpl
margin-right: 5px;
}
.search {
margin-bottom: 10px;
}
.ui-progressbar {
height: 20px;
line-height: 18px;
@ -769,122 +760,38 @@ td.tvShow a:hover {
/* =======================================================================
home_addShows.tmpl
========================================================================== */
#addShowPortal {
width: 700px;
padding: 10px 0;
margin-right: auto;
margin-left: auto;
}
#addShowPortal a {
padding: 10px;
}
div.button {
display: table-cell;
vertical-align: middle;
padding-left: 10px;
}
div.buttontext {
display: table-cell;
padding-left: 20px;
text-align: left;
white-space: normal;
}
div.buttontext h3 {
margin-top: 10px;
}
div.buttontext p {
font-size: 13px;
}
.icon-addnewshow {
background-image: url("../images/addshows/add-new32-black.png");
width: 32px;
height: 32px;
}
.icon-addtrendingshow {
background-image: url("../images/addshows/add-trending32-black.png");
width: 32px;
height: 32px;
}
.icon-addrecommendedshow {
background-image: url("../images/addshows/add-trakt32-black.png");
width: 32px;
height: 32px;
}
.icon-addexistingshow {
background-image: url("../images/addshows/add-existing32-black.png");
width: 32px;
height: 32px;
}
/* =======================================================================
home_newShow.tmpl
========================================================================== */
#addShowForm, #recommendedShowsForm {
margin-left: auto;
margin-right: auto;
}
#newShowPortal {
width: 800px;
padding: 10px 0;
margin-right: auto;
margin-left: auto;
}
#displayText {
padding: 8px;
overflow: hidden;
font-size: 14px;
background-color: #efefef;
border: 1px solid #dfdede;
}
#searchResults input[type="radio"] {
vertical-align: -2px;
border-color: #dfdede;
}
/* =======================================================================
home_addExistingShow.tmpl
========================================================================== */
.existingtabs {
padding: 1em 1.4em;
}
ul#rootDirStaticList {
width: 90%;
margin-right: auto;
margin-left: auto;
text-align: left;
}
ul#rootDirStaticList li {
padding: 4px 5px 4px 5px;
margin: 2px;
list-style: none outside none;
cursor: pointer;
background: url('../css/lib/images/ui-bg_highlight-soft_75_efefef_1x100.png') repeat-x scroll 50% 50% #EFEFEF;
}
ul#rootDirStaticList li label {
margin-top: 5px;
margin-bottom: 5px;
}
ul#rootDirStaticList li input[type="checkbox"] {
vertical-align: -2px;
}
/* =======================================================================
home_trendingShows.tmpl
========================================================================== */
@ -1518,32 +1425,6 @@ select .selected {
border-top: 1px dotted #666666;
}
[class^="icon16-"], [class*=" icon16-"] {
background-image: url("../images/glyphicons-config.png");
background-repeat: no-repeat;
display: inline-block;
height: 16px;
vertical-align: text-top;
width: 16px;
margin-top: 1px;
}
.icon16-github {
background-position: 0 0;
}
.icon16-mirc {
background-position: -26px 0;
}
.icon16-sg {
background-position: -52px 0;
}
.icon16-web {
background-position: -78px 0;
}
.icon16-win {
background-position: -104px 0;
}
/* =======================================================================
config_postProcessing.tmpl
========================================================================== */
@ -1848,10 +1729,6 @@ body {
color: #000;
}
html * {
outline: 0 !important;
}
input[type="radio"] {
margin: 2px 0px 0px;
line-height: normal;
@ -1875,8 +1752,12 @@ input, textarea, select, .uneditable-input {
.navbar-default {
background-color: #333333;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555', endColorstr='#333333');
background: -webkit-gradient(linear, left top, left bottom, from(#555), to(#333));
background: -moz-linear-gradient(top, #555, #333);
background-image: -ms-linear-gradient(top, #555555 0%, #333333 100%);
background-image: -moz-linear-gradient(top, #555555 0%, #333333 100%);
background-image: -o-linear-gradient(top, #555555 0%, #333333 100%);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #555555), color-stop(1, #333333));
background-image: -webkit-linear-gradient(top, #555555 0%, #333333 100%);
background-image: linear-gradient(to bottom, #555555 0%, #333333 100%);
border-color: #3e3f3a;
}
@ -2058,7 +1939,6 @@ fieldset[disabled] .navbar-default .btn-link:focus {
.btn:hover {
color: #333333;
text-decoration: none;
background-color: #e6e6e6;
*background-color: #d9d9d9;
background-position: 0 -15px;
@ -2089,8 +1969,7 @@ fieldset[disabled] .navbar-default .btn-link:focus {
.btn.disabled,
.btn[disabled] {
cursor: default;
background-color: #e6e6e6;
background-image: none;
background: #e6e6e6 none;
opacity: 0.65;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
@ -2098,19 +1977,6 @@ fieldset[disabled] .navbar-default .btn-link:focus {
box-shadow: none;
}
.btn-large {
padding: 9px 14px;
font-size: 15px;
line-height: normal;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.btn-large [class^="icon-"] {
margin-top: 1px;
}
.btn-small {
padding: 5px 9px;
font-size: 11px;
@ -2370,60 +2236,16 @@ pre {
/* =======================================================================
input sizing (for config pages)
========================================================================== */
#pickShow optgroup,
#editAProvider optgroup {
color: #eee;
background-color: #888;
background-color: #888;
}
#pickShow optgroup option,
#editAProvider optgroup option {
color: #222;
background-color: #fff;
}
#config select {
min-width: 0;
width: auto;
display: inline;
margin-top: -4px;
}
.btn-inline {
margin-top: -3px;
}
.input75 {
width: 75px;
margin-top: -4px;
}
.input100 {
width: 100px;
margin-top: -4px;
}
.input150 {
width: 150px;
margin-top: -4px;
}
.input200 {
width: 200px;
margin-top: -4px;
}
.input250 {
width: 250px;
margin-top: -4px;
}
.input300 {
width: 300px;
margin-top: -4px;
}
.input350 {
width: 350px;
margin-top: -4px;
background-color: #fff;
}
/* =======================================================================
@ -2501,92 +2323,31 @@ browser.css
/* =======================================================================
formWizard.css
formWizard
========================================================================== */
fieldset.sectionwrap {
width: 800px;
padding: 5px;
text-align: left;
border-width: 0;
}
.step,
legend.legendStep {
color: #57442b;
margin-bottom: 0px;
}
div.stepsguide {
margin-bottom: 15px;
overflow: hidden;
text-align: left;
cursor: pointer;
}
div.stepsguide .step {
float: left;
width: 266px;
font: bold 24px Arial;
}
div.stepsguide .step p {
margin: 12px 0;
border-bottom: 4px solid #57442b;
border-color: #57442b;
}
div.stepsguide .disabledstep {
.disabledstep {
color: #c4c4c4;
}
div.stepsguide .disabledstep p {
border-bottom: 4px solid #8a775e;
}
div.stepsguide .step .smalltext {
font-size: 13px;
font-weight: normal;
}
div.formpaginate {
width: 800px;
margin-top: 1em;
overflow: auto;
font-weight: bold;
text-align: center;
border-color: #8a775e;
}
div.formpaginate .prev, div.formpaginate .next {
padding: 3px 6px;
color: #fff;
cursor: hand;
cursor: pointer;
background: #57442b;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
.stepDiv {
padding: 15px;
}
/* step 3 related */
#customQuality {
display: block;
padding: 10px 0;
overflow: hidden;
clear: both;
}
#customQualityWrapper div.component-group-desc {
float: left;
width: 165px;
}
#customQualityWrapper div.component-group-desc p {
width: 85%;
margin: .8em 0;
font-size: 1.2em;
color: #666;
}
@ -2606,10 +2367,7 @@ tablesorter.css
.tablesorter th,
.tablesorter td {
padding: 4px;
border-top: #fff 1px solid;
border-left: #fff 1px solid;
vertical-align: middle;
border-color: #fff;
}
/* remove extra border from left edge */
@ -2620,32 +2378,23 @@ tablesorter.css
.tablesorter th {
color: #fff;
text-align: center;
text-shadow: -1px -1px 0 rgba(0,0,0,0.3);
background-color: #333;
border-collapse: collapse;
font-weight: normal;
}
.tablesorter .tablesorter-header {
padding: 4px 18px 4px 18px;
cursor: pointer;
background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAAP///////yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==);
background-position: center right;
background-repeat: no-repeat;
background: #333 url(data:image/gif;base64,R0lGODlhFQAJAIAAAP///////yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==) no-repeat center right;
/* background-image: url(../images/tablesorter/bg.gif); */
}
.tablesorter thead .tablesorter-headerDesc {
background-color: #555;
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7);
background: #555 url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7) no-repeat center right;
/* background-image: url(../images/tablesorter/asc.gif); */
}
.tablesorter thead .tablesorter-headerAsc {
background-color: #555;
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7);
/* background-image: url(../images/tablesorter/desc.gif); */
background: #555 url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7) no-repeat center right;
/* background-image: url(../images/tablesorter/desc.gif); */
}
.tablesorter thead .sorter-false {
@ -2707,7 +2456,6 @@ thead.tablesorter-stickyHeader {
.tablesorter tfoot a {
color:#fff;
text-decoration: none;
}
/* =======================================================================
@ -2844,6 +2592,25 @@ span.token-input-delete-token {
margin: 0 1px;
}
.step-one #searchResults .alt {
background-color: rgb(245, 245, 245);
}
#addRootDirTable td label .filepath,
.grey-text {
color:#666
}
#newShowPortal #displayText .show-name,
#newShowPortal #displayText .show-dest,
#newShowPortal #displayText p {
color: rgb(153, 153, 153);
}
#newShowPortal #displayText .show-name,
#newShowPortal #displayText .show-dest {
color: rgb(87, 68, 43);
}
/* =======================================================================
jquery.confirm.css
========================================================================== */

View file

@ -167,12 +167,7 @@ inc_top.tmpl
background-image: url("../images/menu/menu-icons-white.png");
}
[class^="icon16-"],
[class*=" icon16-"] {
background-image: url("../images/glyphicons-config.png");
}
.ui-autocomplete-loading {
.ui-autocomplete-loading {
background: white url("../images/loading16.gif") right center no-repeat;
}
@ -516,7 +511,6 @@ home.tmpl
border: 1px solid #ccc;
overflow: hidden;
height: 66px;
overflow: hidden;
border-radius: 8px;
vertical-align: top;
width: 360px;
@ -535,9 +529,13 @@ home.tmpl
margin-right: 5px;
}
.search {
margin-bottom: 10px;
}
#HomeLayout { margin-top: -35px; }
#HomeLayout.not-poster { height: 75px }
#HomeLayout div.not-poster { position:relative; top:38px; }
#HomeLayout span.not-poster { margin-top: -30px }
#HomeLayout.poster { margin-top: -35px; }
#HomeLayout span.poster { margin-bottom:10px }
#search_show_name { margin-top: 0 }
.ui-progressbar {
height: 20px;
@ -793,31 +791,42 @@ home_addShows.tmpl
========================================================================== */
#addShowPortal {
width: 700px;
width: 750px;
padding: 10px 0;
margin-right: auto;
margin-left: auto;
}
#addShowPortal a {
padding: 10px;
/* padding: 10px;*/
padding: 0px 20px;
width: 360px;
float: left;
margin: 0 15px 15px 0;
}
div.button {
display: table-cell;
vertical-align: middle;
padding-left: 10px;
/* padding-left: 10px;*/
}
div.buttontext {
display: table-cell;
/*
padding-left: 20px;
padding: 10px 15px;
*/
padding: 10px 0px 10px 15px;
text-align: left;
white-space: normal;
}
div.buttontext p {
margin: 0
}
div.buttontext h3 {
margin-top: 10px;
margin-top: 0px;
}
div.buttontext p {
@ -851,13 +860,19 @@ div.buttontext p {
/* =======================================================================
home_newShow.tmpl
========================================================================== */
#addShowForm, #recommendedShowsForm {
#addShowForm,
#newShowPortal,
fieldset.sectionwrap,
div.formpaginate {
width: 801px
}
#addShowForm {
margin-left: auto;
margin-right: auto;
}
#newShowPortal {
width: 800px;
padding: 10px 0;
margin-right: auto;
margin-left: auto;
@ -867,20 +882,44 @@ home_newShow.tmpl
padding: 8px;
overflow: hidden;
font-size: 14px;
background-color: #efefef;
border: 1px solid #dfdede;
border: 1px solid;
}
#searchResults input[type="radio"] {
vertical-align: -2px;
}
#addShowForm #promptForSettings,
#addShowForm #rootDirStaticList input,
#addShowForm div.field-pair input,
#addShowForm div.field-pair select {
margin-right: 6px;
}
#addShowForm #customQualityWrapper div.component-group-desc p {
font-size: 14px;
}
#addShowForm #nameToSearch {
width: 460px;
margin-top: 0
}
#addShowForm #providedIndexer,
#addShowForm #indexerLangSelect {
margin-left: 7px
}
#addShowForm #searchName {
margin-left: 10px
}
/* =======================================================================
home_addExistingShow.tmpl
========================================================================== */
.existingtabs {
padding: 1em 1.4em;
padding: 20px;
}
ul#rootDirStaticList {
@ -888,14 +927,15 @@ ul#rootDirStaticList {
margin-right: auto;
margin-left: auto;
text-align: left;
list-style-type: none;
padding: 0
}
ul#rootDirStaticList li {
padding: 4px 5px 4px 5px;
margin: 2px;
padding: 4px 10px;
margin: 2px 0;
list-style: none outside none;
cursor: pointer;
background: url('../css/lib/images/ui-bg_highlight-soft_75_efefef_1x100.png') repeat-x scroll 50% 50% #EFEFEF;
}
ul#rootDirStaticList li label {
@ -1538,15 +1578,27 @@ config*.tmpl
color: #666;
}
#config div.field-pair {
padding: 12px 0px;
.stepDiv #customQualityWrapper h4 {
margin-top: 6px;
padding: 0 0
}
.stepDiv div.field-pair {
padding: 0 0 10px
}
#config div.field-pair {
padding: 12px 0
}
.stepDiv .component-desc select,
.stepDiv .component-desc input,
#config div.field-pair select,
#config div.field-pair input {
margin-right: 15px;
}
.stepDiv .component-desc input,
#config div.field-pair input {
float: left;
}
@ -1555,6 +1607,11 @@ config*.tmpl
padding-left: 20px;
}
.stepDiv span.component-title.input {
line-height: 30px
}
.stepDiv span.component-title,
#config span.component-title {
float: left;
width: 172px;
@ -1563,6 +1620,11 @@ config*.tmpl
font-weight: bold;
}
#addShowForm .stepDiv span.component-desc {
width:578px;
}
.stepDiv span.component-desc,
#config span.component-desc {
font-size: 12px;
font-weight: normal;
@ -1684,14 +1746,14 @@ select .selected {
border-top: 1px dotted #666666;
}
[class^="icon16-"], [class*=" icon16-"] {
background-image: url("../images/glyphicons-config.png");
background-repeat: no-repeat;
display: inline-block;
[class^="icon16-"],
[class*=" icon16-"] {
background: url('../images/glyphicons-config.png') no-repeat;
display: block;
height: 16px;
vertical-align: text-top;
width: 16px;
margin-top: 1px;
float: left;
margin-right: 5px;
}
.icon16-github {
@ -2298,9 +2360,9 @@ fieldset[disabled] .navbar-default .btn-link:focus {
border-radius: 5px;
}
.btn-large [class^="icon-"] {
/*.btn-large [class^="icon-"] {
margin-top: 1px;
}
}*/
.btn-small {
padding: 5px 9px;
@ -2681,45 +2743,46 @@ browser.css
/* =======================================================================
formWizard.css
formWizard
========================================================================== */
fieldset.sectionwrap {
width: 800px;
padding: 5px;
padding: 5px 0;
text-align: left;
border-width: 0;
}
.step-one #searchResults legend.legendStep {
margin-top: 10px;
}
div.stepsguide .step,
legend.legendStep {
color: #57442b;
margin-bottom: 0px;
margin-bottom: 0;
}
legend.legendStep p {
padding-left: 15px;
}
div.stepsguide {
margin-bottom: 15px;
overflow: hidden;
text-align: left;
cursor: pointer;
}
div.stepsguide .step {
float: left;
width: 266px;
width: 267px;
font: bold 24px Arial;
}
div.stepsguide .step p {
margin: 12px 0;
border-bottom: 4px solid #57442b;
}
div.stepsguide .disabledstep {
color: #c4c4c4;
margin: 12px 0 0;
border-bottom: 5px solid;
}
div.stepsguide .disabledstep p {
border-bottom: 4px solid #8a775e;
border-bottom: 2px solid;
}
div.stepsguide .step .smalltext {
@ -2728,56 +2791,41 @@ div.stepsguide .step .smalltext {
}
div.formpaginate {
width: 800px;
margin-top: 1em;
overflow: auto;
font-weight: bold;
text-align: center;
}
.step-outer {
overflow: hidden
}
div.formpaginate .prev, div.formpaginate .next {
padding: 3px 6px;
color: #fff;
cursor: hand;
cursor: pointer;
background: #57442b;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
.stepDiv {
padding: 15px;
}
.stepDiv.parent-folder {
padding: 15px 0 0;
width: 430px;
margin: 0px auto;
}
#tabs .nocheck,
.stepDiv .nocheck {
padding-left: 16px;
}
#tabs label span.component-title,
.stepDiv label span.component-title {
padding-bottom: 6px;
float: left;
width: 172px;
margin-right: 10px;
font-size: 13px;
font-weight: bold;
}
#tabs label span.component-desc,
.stepDiv label span.component-desc {
padding-bottom: 6px;
font-size: 12px;
font-weight: normal;
display:inline-block;
width:475px;
}
/* step 3 related */
#customQuality {
display: block;
padding: 10px 0;
padding: 0 0 10px 0;
overflow: hidden;
clear: both;
}
@ -2791,7 +2839,6 @@ div.formpaginate .prev, div.formpaginate .next {
width: 85%;
margin: .8em 0;
font-size: 1.2em;
color: #666;
}
/* =======================================================================
@ -2811,11 +2858,15 @@ tablesorter.css
.tablesorter th,
.tablesorter td {
padding: 4px;
border-top: #fff 1px solid;
border-left: #fff 1px solid;
border-top: 1px solid;
border-left: 1px solid;
vertical-align: middle;
}
#addRootDirTable.tablesorter td {
vertical-align: baseline;
}
/* remove extra border from left edge */
.tablesorter th:first-child,
.tablesorter td:first-child {
@ -2823,10 +2874,7 @@ tablesorter.css
}
.tablesorter th {
color: #fff;
text-align: center;
text-shadow: -1px -1px 0 rgba(0,0,0,0.3);
background-color: #333;
border-collapse: collapse;
font-weight: normal;
}
@ -2834,22 +2882,7 @@ tablesorter.css
.tablesorter .tablesorter-header {
padding: 4px 18px 4px 18px;
cursor: pointer;
background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAAP///////yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==);
background-position: center right;
background-repeat: no-repeat;
/* background-image: url(../images/tablesorter/bg.gif); */
}
.tablesorter thead .tablesorter-headerDesc {
background-color: #555;
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7);
/* background-image: url(../images/tablesorter/asc.gif); */
}
.tablesorter thead .tablesorter-headerAsc {
background-color: #555;
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7);
/* background-image: url(../images/tablesorter/desc.gif); */
vertical-align: middle;
}
.tablesorter thead .sorter-false {
@ -2910,7 +2943,6 @@ thead.tablesorter-stickyHeader {
}
.tablesorter tfoot a {
color:#fff;
text-decoration: none;
}
@ -3053,6 +3085,42 @@ span.token-input-delete-token {
margin: 0 1px;
}
.stepDiv #searchResults div {
line-height: 1.7;
}
.stepDiv #searchResults #searchingAnim {
margin-right: 6px;
}
.stepone-result-title {
font-weight: 600;
margin-left: 10px
}
.stepone-result-date,
.stepone-result-db,
.stepone-result-overview {
margin-left: 5px
}
.stepone-result-db img {
margin-top: 3px;
vertical-align: top;
}
#newShowPortal #displayText .show-name,
#newShowPortal #displayText .show-dest,
#newShowPortal #displayText p {
margin: 0;
}
#newShowPortal #displayText .show-name,
#newShowPortal #displayText .show-dest {
font-weight: 600;
}
#addRootDirTable td label .filepath {
font-weight: 900
}
.boldest {font-weight: 900}
.red-text {color:#d33}
.clear-left {clear:left}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 B

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 B

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 977 B

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

After

Width:  |  Height:  |  Size: 213 B

View file

@ -1,102 +0,0 @@
#import os.path
#import datetime
#import locale
#import sickbeard
#from sickbeard.common import *
#from sickbeard.sbdatetime import *
#from sickbeard import config
#from sickbeard import metadata
#from sickbeard.metadata.generic import GenericMetadata
#set global $title = "Config - Backup/Restore"
#set global $header = "Backup/Restore"
#set global $sbPath="../.."
#set global $topmenu="config"#
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
<script type="text/javascript" src="$sbRoot/js/configBackupRestore.js?$sbPID"></script>
#if $varExists('header')
<h1 class="header">$header</h1>
#else
<h1 class="title">$title</h1>
#end if
#set $indexer = 0
#if $sickbeard.INDEXER_DEFAULT
#set $indexer = $sickbeard.INDEXER_DEFAULT
#end if
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
<div id="config">
<div id="config-content">
<form name="configForm" method="post" action="backuprestore">
<div id="config-components">
<ul>
<li><a href="#core-component-group1">Backup</a></li>
<li><a href="#core-component-group2">Restore</a></li>
</ul>
<div id="core-component-group1" class="component-group clearfix">
<div class="component-group-desc">
<h3>Backup</h3>
<p><b>Backup your main database file and config.</b></p>
</div>
<fieldset class="component-group-list">
<div class="field-pair">
Select the folder you wish to save your backup file to:
<br/><br/>
<input type="text" name="backupDir" id="backupDir" class="form-control input-sm input350" />
<input class="btn btn-inline" type="button" value="Backup" id="Backup" />
<br/>
</div>
<div class="Backup" id="Backup-result"></div>
</fieldset>
</div><!-- /component-group1 //-->
<div id="core-component-group2" class="component-group clearfix">
<div class="component-group-desc">
<h3>Restore</h3>
<p><b>Restore your main database file and config.</b></p>
</div>
<fieldset class="component-group-list">
<div class="field-pair">
Select the backup file you wish to restore:
<br/><br/>
<input type="text" name="backupFile" id="backupFile" class="form-control input-sm input350" />
<input class="btn btn-inline" type="button" value="Restore" id="Restore" />
<br/>
</div>
<div class="Restore" id="Restore-result"></div>
</fieldset>
</div><!-- /component-group2 //-->
</div><!-- /config-components -->
</form>
</div>
</div>
<div class="clearfix"></div>
<script type="text/javascript" charset="utf-8">
<!--
jQuery('#backupDir').fileBrowser({ title: 'Select backup folder to save to', key: 'backupPath' });
jQuery('#backupFile').fileBrowser({ title: 'Select backup files to restore', key: 'backupFile', includeFiles: 1 });
jQuery('#config-components').tabs();
//-->
</script>
#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl")

View file

@ -215,6 +215,16 @@
</label>
</div>
<div class="field-pair">
<label for="home_search_focus">
<span class="component-title">Give show list search focus</span>
<span class="component-desc">
<input type="checkbox" name="home_search_focus" id="home_search_focus" #if $sickbeard.HOME_SEARCH_FOCUS then 'checked="checked"' else ''#/>
<p>page refresh on "Show List" will start search box focused</p>
</span>
</label>
</div>
<div class="field-pair">
<label for="sort_article">
<span class="component-title">Sort with "The", "A", "An"</span>

View file

@ -159,7 +159,7 @@
<div class="component-group-desc">
<img class="notifier-icon" src="$sbRoot/images/notifiers/plex.png" alt="" title="Plex Media Server" />
<h3><a href="<%= anon_url('http://www.plexapp.com/') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">Plex Media Server</a></h3>
<p>Experience your media on a visually stunning, easy to use interface on your Mac connected to your TV. Your media library has never looked this good!</p>
<p>Plex organizes all of your personal media, wherever you keep it, so you can enjoy it on any device</p>
<p class="plexinfo hide">For sending notifications to Plex Home Theater (PHT) clients, use the XBMC notifier with port <b>3005</b>.</p>
</div>
<fieldset class="component-group-list">
@ -222,12 +222,12 @@
</div>
<div class="field-pair">
<label for="plex_host">
<span class="component-title">Plex Client IP:Port</span>
<span class="component-title">Plex client IP:Port</span>
<input type="text" name="plex_host" id="plex_host" value="$sickbeard.PLEX_HOST" class="form-control input-sm input350" />
</label>
<label>
<span class="component-title">&nbsp;</span>
<span class="component-desc">host running Plex Client (eg. 192.168.1.100:3000)</span>
<span class="component-desc">host running Plex client (eg. 192.168.1.100:3000)</span>
</label>
<label>
<span class="component-title">&nbsp;</span>
@ -236,22 +236,22 @@
</div>
<div class="field-pair">
<label for="plex_username">
<span class="component-title">Plex Client username</span>
<span class="component-title">Plex client username</span>
<input type="text" name="plex_username" id="plex_username" value="$sickbeard.PLEX_USERNAME" class="form-control input-sm input250" />
</label>
<label>
<span class="component-title">&nbsp;</span>
<span class="component-desc">username for your Plex client API (blank for none)</span>
<span class="component-desc">needed if your client / server requires authentication (blank for none)</span>
</label>
</div>
<div class="field-pair">
<label for="plex_password">
<span class="component-title">Plex Client password</span>
<span class="component-title">Plex client password</span>
<input type="password" name="plex_password" id="plex_password" value="$sickbeard.PLEX_PASSWORD" class="form-control input-sm input250" />
</label>
<label>
<span class="component-title">&nbsp;</span>
<span class="component-desc">password for your Plex client API (blank for none)</span>
<span class="component-desc">needed if your client / server requires authentication (blank for none)</span>
</label>
</div>
<div class="testNotification" id="testPLEX-result">Click below to test.</div>

View file

@ -42,7 +42,7 @@
$(this).qtip({
show: {solo:true},
position: {viewport:$(window), my:'left center', adjust:{ y: -10, x: 2 }},
style: {tip:{corner:true, method:'polygon'}, classes:'qtip-rounded qtip-shadow ui-tooltip-sb'}
style: {classes:'qtip-rounded qtip-shadow'}
});
});
#end raw
@ -52,7 +52,7 @@
};
\$('.imdbstars').generateStars();
TVShowList = [${tvshow_id_csv}]
});
//-->
</script>

View file

@ -65,7 +65,6 @@
<input type="hidden" name="show" value="$show.indexerid" />
<b>Location:</b> <input type="text" name="location" id="location" value="$show._location" class="form-control form-control-inline input-sm input350" /><br />
<br />
<b>Quality:</b>
#set $qualities = $common.Quality.splitQuality(int($show.quality))
#set global $anyQualities = $qualities[0]
#set global $bestQualities = $qualities[1]

View file

@ -49,110 +49,112 @@
<!--
\$.tablesorter.addParser({
id: 'loadingNames',
is: function(s) {
return false;
},
format: function(s) {
if (s.indexOf('Loading...') == 0)
return s.replace('Loading...','000');
id: 'loadingNames',
is: function(s) {
return false;
},
format: function(s) {
if (s.indexOf('Loading...') == 0)
return s.replace('Loading...','000');
else
#if not $sickbeard.SORT_ARTICLE:
return (s || '').replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1');
#else:
return (s || '');
#end if
},
type: 'text'
},
type: 'text'
});
\$.tablesorter.addParser({
id: 'quality',
is: function(s) {
return false;
},
format: function(s) {
return s.replace('hd1080p',5).replace('hd720p',4).replace('hd',3).replace('sd',2).replace('any',1).replace('custom',7);
},
type: 'numeric'
id: 'quality',
is: function(s) {
return false;
},
format: function(s) {
return s.replace('hd1080p',5).replace('hd720p',4).replace('hd',3).replace('sd',2).replace('any',1).replace('custom',7);
},
type: 'numeric'
});
\$(document).ready(function(){
\$("img#network").on('error', function(){
\$(this).parent().text(\$(this).attr('alt'));
\$(this).remove();
});
\$("img#network").on('error', function(){
\$(this).parent().text(\$(this).attr('alt'));
\$(this).remove();
});
\$("#showListTableShows:has(tbody tr)").tablesorter({
sortList: [[5,1],[1,0]],
textExtraction: {
\$("#showListTableShows:has(tbody tr)").tablesorter({
sortList: [[5,1],[1,0]],
textExtraction: {
0: function(node) { return \$(node).find("span").text().toLowerCase(); },
2: function(node) { return \$(node).find("span").text().toLowerCase(); },
3: function(node) { return \$(node).find("span").text().toLowerCase(); },
4: function(node) { return \$(node).find("span").text(); },
5: function(node) { return \$(node).find("img").attr("alt"); }
},
widgets: ['saveSort', 'zebra', 'stickyHeaders', 'filter'],
headers: {
},
widgets: ['saveSort', 'zebra', 'stickyHeaders', 'filter'],
headers: {
0: { sorter: 'isoDate' },
1: { sorter: 'loadingNames' },
3: { sorter: 'quality' },
4: { sorter: 'eps' }
},
},
widgetOptions : {
filter_columnFilters: false,
filter_columnFilters: false,
filter_reset: '.resetshows'
},
sortStable: true
});
sortStable: true
});
\$("#showListTableAnime:has(tbody tr)").tablesorter({
sortList: [[5,1],[1,0]],
textExtraction: {
sortList: [[5,1],[1,0]],
textExtraction: {
0: function(node) { return \$(node).find("span").text().toLowerCase(); },
2: function(node) { return \$(node).find("span").text().toLowerCase(); },
3: function(node) { return \$(node).find("span").text().toLowerCase(); },
4: function(node) { return \$(node).find("span").text(); },
5: function(node) { return \$(node).find("img").attr("alt"); }
},
widgets: ['saveSort', 'zebra', 'stickyHeaders', 'filter'],
headers: {
},
widgets: ['saveSort', 'zebra', 'stickyHeaders', 'filter'],
headers: {
0: { sorter: 'isoDate' },
1: { sorter: 'loadingNames' },
3: { sorter: 'quality' },
4: { sorter: 'eps' }
},
},
widgetOptions : {
filter_columnFilters: false,
filter_columnFilters: false,
filter_reset: '.resetanime'
},
sortStable: true
});
sortStable: true
});
\$.tablesorter.filter.bindSearch( "#showListTableShows", \$('.search') );
#if $sickbeard.ANIME_SPLIT_HOME:
#if $sickbeard.ANIME_SPLIT_HOME:
\$.tablesorter.filter.bindSearch( "#showListTableAnime", \$('.search') );
#end if
#end if
#set $fuzzydate = 'airdate'
#if $sickbeard.FUZZY_DATING:
fuzzyMoment({
dtInline : #if $layout == 'poster' then "true" else "false"#,
containerClass : '.${fuzzydate}',
dateHasTime : false,
dateFormat : '${sickbeard.DATE_PRESET}',
timeFormat : '${sickbeard.TIME_PRESET}',
trimZero : #if $sickbeard.TRIM_ZERO then "true" else "false"#
});
fuzzyMoment({
dtInline : #if $layout == 'poster' then "true" else "false"#,
containerClass : '.${fuzzydate}',
dateHasTime : false,
dateFormat : '${sickbeard.DATE_PRESET}',
timeFormat : '${sickbeard.TIME_PRESET}',
trimZero : #if $sickbeard.TRIM_ZERO then "true" else "false"#
});
#end if
var \$container = [\$('#container'), \$('#container-anime')];
jQuery.each(\$container, function (j) {
this.isotope({
this.isotope({
itemSelector: '.show',
sortBy : '$sickbeard.POSTER_SORTBY',
sortAscending: $sickbeard.POSTER_SORTDIR,
sortBy : '$sickbeard.POSTER_SORTBY',
sortAscending: $sickbeard.POSTER_SORTDIR,
layoutMode: 'masonry',
masonry: {
columnWidth: 12,
@ -170,7 +172,7 @@
network: '[data-network]',
date: function( itemElem ) {
var date = \$( itemElem ).attr('data-date');
return date.length && parseInt( date, 10 ) || Number.POSITIVE_INFINITY;
return date.length && parseInt( date, 10 ) || Number.POSITIVE_INFINITY;
},
progress: function( itemElem ) {
var progress = \$( itemElem ).attr('data-progress');
@ -184,7 +186,7 @@
var sortValue = this.value;
\$('#container').isotope({ sortBy: sortValue });
\$('#container-anime').isotope({ sortBy: sortValue });
\$.get(this.options[this.selectedIndex].getAttribute('data-sort'));
\$.get(this.options[this.selectedIndex].getAttribute('data-sort'));
});
\$('#postersortdirection').on( 'change', function() {
@ -192,57 +194,64 @@
sortDirection = sortDirection == 'true';
\$('#container').isotope({ sortAscending: sortDirection });
\$('#container-anime').isotope({ sortAscending: sortDirection });
\$.get(this.options[this.selectedIndex].getAttribute('data-sort'));
\$.get(this.options[this.selectedIndex].getAttribute('data-sort'));
});
#if $sickbeard.HOME_SEARCH_FOCUS
\$('#search_show_name').focus();
#end if
});
//-->
</script>
#if $varExists('header')
<h1 class="header">$header</h1>
<h1 class="header" style="margin-bottom:0">$header</h1>
#else
<h1 class="title">$title</h1>
<h1 class="title" style="margin-bottom:0">$title</h1>
#end if
<div id="HomeLayout" class="pull-right" style="margin-top: -40px;">
<span> Layout:
<select name="layout" class="form-control form-control-inline input-sm" onchange="location = this.options[this.selectedIndex].value;">
#set $tab = 1
#if $layout != 'poster':
<div id="HomeLayout" class="pull-right not-poster">
<div class="not-poster">
<input id="search_show_name" class="search form-control form-control-inline input-sm input200" type="search" data-column="1" placeholder="Search Show Name" tabindex="$tab#set $tab += 1#">
&nbsp;<button type="button" class="resetshows resetanime btn btn-inline" tabindex="$tab#set $tab += 1#">Reset Search</button>
</div>
<span class="pull-right not-poster"> Layout:
#else
<div id="HomeLayout" class="pull-right poster">
<span class="pull-right poster">Layout:
#end if
<select name="layout" class="form-control form-control-inline input-sm" onchange="location = this.options[this.selectedIndex].value;" tabindex="$tab#set $tab += 1#">
<option value="$sbRoot/setHomeLayout/?layout=poster" #if $sickbeard.HOME_LAYOUT == "poster" then "selected=\"selected\"" else ""#>Poster</option>
<option value="$sbRoot/setHomeLayout/?layout=small" #if $sickbeard.HOME_LAYOUT == "small" then "selected=\"selected\"" else ""#>Small Poster</option>
<option value="$sbRoot/setHomeLayout/?layout=banner" #if $sickbeard.HOME_LAYOUT == "banner" then "selected=\"selected\"" else ""#>Banner</option>
<option value="$sbRoot/setHomeLayout/?layout=simple" #if $sickbeard.HOME_LAYOUT == "simple" then "selected=\"selected\"" else ""#>Simple</option>
</select>
</span>
#if $layout == 'poster':
&nbsp;
<span> Sort By:
<select id="postersort" class="form-control form-control-inline input-sm">
<option value="name" data-sort="$sbRoot/setPosterSortBy/?sort=name" #if $sickbeard.POSTER_SORTBY == "name" then "selected=\"selected\"" else ""#>Name</option>
<option value="date" data-sort="$sbRoot/setPosterSortBy/?sort=date" #if $sickbeard.POSTER_SORTBY == "date" then "selected=\"selected\"" else ""#>Next Episode</option>
<option value="network" data-sort="$sbRoot/setPosterSortBy/?sort=network" #if $sickbeard.POSTER_SORTBY == "network" then "selected=\"selected\"" else ""#>Network</option>
<option value="progress" data-sort="$sbRoot/setPosterSortBy/?sort=progress" #if $sickbeard.POSTER_SORTBY == "progress" then "selected=\"selected\"" else ""#>Progress</option>
#if $layout == 'poster':
<span>Sort By:
<select id="postersort" class="form-control form-control-inline input-sm" tabindex="$tab#set $tab += 1#">
<option value="name" data-sort="$sbRoot/setPosterSortBy/?sort=name" #if $sickbeard.POSTER_SORTBY == "name" then "selected=\"selected\"" else ""#>Name</option>
<option value="date" data-sort="$sbRoot/setPosterSortBy/?sort=date" #if $sickbeard.POSTER_SORTBY == "date" then "selected=\"selected\"" else ""#>Next Episode</option>
<option value="network" data-sort="$sbRoot/setPosterSortBy/?sort=network" #if $sickbeard.POSTER_SORTBY == "network" then "selected=\"selected\"" else ""#>Network</option>
<option value="progress" data-sort="$sbRoot/setPosterSortBy/?sort=progress" #if $sickbeard.POSTER_SORTBY == "progress" then "selected=\"selected\"" else ""#>Progress</option>
</select>
</span>
&nbsp;
<span> Sort Order:
<select id="postersortdirection" class="form-control form-control-inline input-sm">
<option value="true" data-sort="$sbRoot/setPosterSortDir/?direction=1" #if $sickbeard.POSTER_SORTDIR == 1 then "selected=\"selected\"" else ""#>Asc</option>
<option value="false" data-sort="$sbRoot/setPosterSortDir/?direction=0" #if $sickbeard.POSTER_SORTDIR == 0 then "selected=\"selected\"" else ""#>Desc</option>
<span style="margin:0 0 0 5px">Sort Order:
<select id="postersortdirection" class="form-control form-control-inline input-sm" tabindex="$tab#set $tab += 1#">
<option value="true" data-sort="$sbRoot/setPosterSortDir/?direction=1" #if $sickbeard.POSTER_SORTDIR == 1 then "selected=\"selected\"" else ""#>Asc</option>
<option value="false" data-sort="$sbRoot/setPosterSortDir/?direction=0" #if $sickbeard.POSTER_SORTDIR == 0 then "selected=\"selected\"" else ""#>Desc</option>
</select>
</span>
&nbsp;
#end if
#end if
</div>
#if $layout != 'poster':
<div class="pull-right">
<input class="search form-control form-control-inline input-sm input200" type="search" data-column="1" placeholder="Search Show Name"> <button type="button" class="resetshows resetanime btn btn-inline">Reset Search</button>
</div>
#end if
#for $curShowlist in $showlists:
#set $curListType = $curShowlist[0]
@ -256,11 +265,11 @@
<div class="posterview">
#for $curLoadingShow in $sickbeard.showQueueScheduler.action.loadingShowList:
#if $curLoadingShow.show != None and $curLoadingShow.show in $sickbeard.showList:
#continue
#end if
#if $curLoadingShow.show == None:
#if $curLoadingShow.show != None and $curLoadingShow.show in $sickbeard.showList:
#continue
#end if
#if $curLoadingShow.show == None:
<div class="show" data-name="0" data-date="010101" data-network="0" data-progress="101">
<img alt="" title="$curLoadingShow.show_name" class="show-image" style="border-bottom: 1px solid #111;" src="$sbRoot/images/poster.png" />
<div class="show-details">
@ -269,7 +278,7 @@
</div>
#end if
#end for
$myShowList.sort(lambda x, y: cmp(x.name, y.name))
@ -328,8 +337,8 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
#set $den = 1
#end if
#set $progressbar_percent = $nom * 100 / $den
#set $progressbar_percent = $nom * 100 / $den
#set $data_date = '6000000000.0'
#if $cur_airs_next:
#set $data_date = $time.mktime($sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($cur_airs_next,$curShow.airs,$curShow.network)).timetuple())
@ -388,9 +397,9 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
#set $output_html = $display_status
#end if
#end if
$output_html
$output_html
#end if
</div>
</div>
<table width="100%" cellspacing="1" border="0" cellpadding="0">
<tr>
@ -399,29 +408,28 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
</td>
<td class="show-table">
#if $layout != 'simple':
#if $curShow.network:
#if $layout != 'simple':
#if $curShow.network:
<img class="show-network-image" src="$sbRoot/images/network/${curShow.network.replace(u"\u00C9",'e').lower()}.png" alt="$curShow.network" title="$curShow.network" />
#else:
#else:
<img class="show-network-image" src="$sbRoot/images/network/nonetwork.png" alt="No Network" title="No Network" />
#end if
#else:
#end if
#else:
$curShow.network
#end if
#end if
</td>
<td class="show-table">
#if $curShow.quality in $qualityPresets:
#if $curShow.quality in $qualityPresets:
<span class="show-quality">$qualityPresetStrings[$curShow.quality]</span>
#else:
#else:
<span class="show-quality">Custom</span>
#end if
#end if
</td>
</tr>
</table>
</div>
#end for
@ -432,7 +440,7 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
<table id="showListTable$curListType" class="tablesorter" cellspacing="1" border="0" cellpadding="0">
<thead>
<thead>
<tr>
<th class="nowrap">Next Ep</th>
<th>Show</th>
@ -443,37 +451,37 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
<th>Status</th>
</tr>
</thead>
<tfoot>
<tr>
<tfoot>
<tr>
<th rowspan="1" colspan="1" align="center"><a href="$sbRoot/home/addShows/">Add Show</a></th>
<th rowspan="1" colspan="6"></th>
</tr>
</tfoot>
</tr>
</tfoot>
<tbody>
#for $curLoadingShow in $sickbeard.showQueueScheduler.action.loadingShowList:
#if $curLoadingShow.show != None and $curLoadingShow.show in $sickbeard.showList:
#continue
#end if
<tr>
<td align="center">(loading)</td>
<td></td>
<td>
#if $curLoadingShow.show == None:
Loading... ($curLoadingShow.show_name)
#else:
<a href="displayShow?show=$curLoadingShow.show.indexerid">$curLoadingShow.show.name</a>
#if $curLoadingShow.show != None and $curLoadingShow.show in $sickbeard.showList:
#continue
#end if
</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td align="center">(loading)</td>
<td>
#if $curLoadingShow.show == None:
Loading... ($curLoadingShow.show_name)
#else:
<a href="displayShow?show=$curLoadingShow.show.indexerid">$curLoadingShow.show.name</a>
#end if
</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
#end for
$myShowList.sort(lambda x, y: cmp(x.name, y.name))
@ -524,58 +532,59 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
#set $den = 1
#end if
#set $progressbar_percent = $nom * 100 / $den
#set $progressbar_percent = $nom * 100 / $den
<tr>
<tr>
#if $cur_airs_next
#if $cur_airs_next
#set $ldatetime = $sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($cur_airs_next,$curShow.airs,$curShow.network))
<td align="center" class="nowrap"><div class="${fuzzydate}">$sbdatetime.sbdatetime.sbfdate($ldatetime)</div><span class="sort_data">$time.mktime($ldatetime.timetuple())</span></td>
#else:
<td align="center" class="nowrap"></td>
<td align="center" class="nowrap"></td>
#end if
#if $layout == 'small':
<td class="tvShow">
<div class="imgsmallposter $layout">
<a href="$sbRoot/showPoster/?show=$curShow.indexerid&amp;which=poster" rel="dialog" title="$curShow.name">
<img src="$sbRoot/showPoster/?show=$curShow.indexerid&amp;which=poster_thumb" class="$layout" alt="$curShow.indexerid"/>
</a>
#if $layout == 'small':
<td class="tvShow">
<div class="imgsmallposter $layout">
<a href="$sbRoot/showPoster/?show=$curShow.indexerid&amp;which=poster" rel="dialog" title="$curShow.name">
<img src="$sbRoot/showPoster/?show=$curShow.indexerid&amp;which=poster_thumb" class="$layout" alt="$curShow.indexerid"/>
</a>
<a href="$sbRoot/home/displayShow?show=$curShow.indexerid" style="vertical-align: middle;">$curShow.name</a>
</div>
</td>
</div>
</td>
#else if $layout == 'banner':
<td>
<span style="display: none;">$curShow.name</span>
<div class="imgbanner $layout">
<a href="$sbRoot/home/displayShow?show=$curShow.indexerid">
<img src="$sbRoot/showPoster/?show=$curShow.indexerid&amp;which=banner" class="$layout" alt="$curShow.indexerid" title="$curShow.name"/>
</div>
</td>
#else if $layout == 'simple':
<td class="tvShow"><a href="$sbRoot/home/displayShow?show=$curShow.indexerid">$curShow.name</a></td>
<td>
<span style="display: none;">$curShow.name</span>
<div class="imgbanner $layout">
<a href="$sbRoot/home/displayShow?show=$curShow.indexerid">
<img src="$sbRoot/showPoster/?show=$curShow.indexerid&amp;which=banner" class="$layout" alt="$curShow.indexerid" title="$curShow.name"/>
</div>
</td>
#else if $layout == 'simple':
<td class="tvShow"><a href="$sbRoot/home/displayShow?show=$curShow.indexerid">$curShow.name</a></td>
#end if
#if $layout != 'simple':
<td align="center">
#if $curShow.network:
<img id="network" width="54" height="27" src="$sbRoot/images/network/${curShow.network.replace(u"\u00C9",'e').lower()}.png" alt="$curShow.network" title="$curShow.network" />
#else:
<img id="network" width="54" height="27" src="$sbRoot/images/network/nonetwork.png" alt="No Network" title="No Network" />
#end if
<span style="display: none;">$curShow.network</span>
<img id="network" width="54" height="27" src="$sbRoot/images/network/${curShow.network.replace(u"\u00C9",'e').lower()}.png" alt="$curShow.network" title="$curShow.network" />
#else:
<img id="network" width="54" height="27" src="$sbRoot/images/network/nonetwork.png" alt="No Network" title="No Network" />
#end if
</td>
#else:
#else:
<td>
$curShow.network
<span>$curShow.network</span>
</td>
#end if
#end if
#if $curShow.quality in $qualityPresets:
#if $curShow.quality in $qualityPresets:
<td align="center"><span class="quality $qualityPresetStrings[$curShow.quality]">$qualityPresetStrings[$curShow.quality]</span></td>
#else:
#else:
<td align="center"><span class="quality Custom">Custom</span></td>
#end if
#end if
<td align="center"><span style="display: none;">$progressbar_percent</span><div id="progressbar$curShow.indexerid" style="position:relative;"></div>
<script type="text/javascript">
<!--
@ -601,12 +610,12 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
//-->
</script>
</td>
<td align="center">
<td align="center">
<img src="$sbRoot/images/#if int($curShow.paused) == 0 and $curShow.status != "Ended" then "yes16.png\" alt=\"Y\"" else "no16.png\" alt=\"N\""# width="16" height="16" />
</td>
<td align="center">
<td align="center">
#set $display_status = $curShow.status
#if None is not $display_status
@ -619,7 +628,7 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
$display_status
</td>
</tr>
</tr>
#end for
</tbody>

View file

@ -1,17 +1,15 @@
#import os.path
#import sickbeard
#from sickbeard.common import *
#set global $title="Existing Show"
#set global $header="Existing Show"
#set global $title = 'Existing Show'
#set global $header = 'Existing Show'
#set global $sbPath="../.."
#set global $sbPath = '../..'
#set global $statpath="../.."#
#set global $topmenu="home"#
#set global $statpath = '../..'
#set global $topmenu = 'home'
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
<form id="addShowForm" method="post" action="$sbRoot/home/addShows/addNewShow" accept-charset="utf-8">
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addExistingShow.js?$sbPID"></script>
@ -21,7 +19,7 @@
<script type="text/javascript" charset="utf-8">
<!--
\$(document).ready(function(){
\$( "#tabs" ).tabs({
\$( '#tabs' ).tabs({
collapsible: true,
selected: #if $sickbeard.ROOT_DIRS then '-1' else '0'#
});
@ -29,41 +27,55 @@
//-->
</script>
#if $varExists('header')
#if $varExists('header')
<h1 class="header">$header</h1>
#else
#else
<h1 class="title">$title</h1>
#end if
<div id="tabs">
<ul>
<li><a href="#tabs-1">Manage Directories</a></li>
<li><a href="#tabs-2">Customize Options</a></li>
</ul>
<div id="tabs-1" class="existingtabs">
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_rootDirs.tmpl")
</div>
<div id="tabs-2" class="existingtabs">
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_addShowOptions.tmpl")
</div>
</div>
<br />
<image class="preload-image" style="position:absolute;top:-999px" src="$sbRoot/images/loading32<%= '-dark' if 'dark' == sickbeard.THEME_NAME else '' %>.gif" width="32" height="32" border="0">
<p>SickGear can add existing shows, using the current options, by using locally stored NFO/XML metadata to eliminate user interaction.<br />
If you would rather have SickGear prompt you to customize each show, then use the checkbox below.</p>
<form id="addShowForm" method="post" action="$sbRoot/home/addShows/addNewShow" accept-charset="utf-8">
<p><input type="checkbox" name="promptForSettings" id="promptForSettings" /> <label for="promptForSettings">Prompt me to set settings for each show</label></p>
<p>Tip: shows are added quicker when usable show nfo and xml metadata is found</p>
<hr />
<p style="margin-top:15px">
<input type="checkbox" id="promptForSettings" name="promptForSettings" style="vertical-align: top;" />
<label for="promptForSettings">Enable to change the following options per show, otherwise use these options with all shows added below</label>
</p>
<h5><b>Displaying folders within these directories which aren't already added to SickGear:</b></h5>
<div id="tabs">
<ul>
<li><a href="#tabs-1">Manage parent folders</a></li>
<li><a href="#tabs-2">Custom options</a></li>
</ul>
<div id="tabs-1" class="existingtabs">
<div style="width: 430px; margin: 0px auto">
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_rootDirs.tmpl')
</div>
</div>
<div id="tabs-2">
<div class="stepDiv">
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_addShowOptions.tmpl')
</div>
</div>
</div>
<ul id="rootDirStaticList"><li></li></ul>
<br />
<hr />
<br />
<div id="tableDiv"></div>
<br />
<input class="btn btn-primary" type="button" value="Submit" id="submitShowDirs" />
<p>The following parent folder(s) are scanned for existing shows. Toggle a folder to display shows</p>
<ul id="rootDirStaticList">
<li></li>
</ul>
<p>shows <span class="boldest">not known</span> to SickGear are listed below...</p>
<div id="tableDiv"></div>
<br />
<input class="btn btn-primary" type="button" value="Submit" id="submitShowDirs" />
</form>
#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl")
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -1,16 +1,16 @@
#import os.path
#import urllib
#import sickbeard
#set global $title="Add Show"
#set global $header="Add Show"
#set global $title = 'Add Show'
#set global $header = 'Add Show'
#set global $sbPath="../.."
#set global $sbPath = '../..'
#set global $statpath="../.."#
#set global $topmenu="home"#
#set global $statpath = '../..'
#set global $topmenu = 'home'
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
#if $varExists('header')
<h1 class="header">$header</h1>
@ -18,46 +18,49 @@
<h1 class="title">$title</h1>
#end if
<div id="addShowPortal">
<div id="addShowPortal">
<a href="$sbRoot/home/addShows/newShow/" id="btnNewShow" class="btn btn-large">
<div class="button"><div class="icon-addnewshow"></div></div>
<div class="buttontext">
<h3>Add New Show</h3>
<p>For shows that you haven't downloaded yet, this option finds a show on theTVDB.com and TVRage.com, creates a directory for its episodes, and adds it to SickGear.</p>
</div>
</a>
<a class="btn btn-large" href="$sbRoot/home/addShows/newShow/">
<div class="button"><div class="icon-addnewshow"></div></div>
<div class="buttontext">
<h3>Add New Show</h3>
<p>Search a TV database for a show to add. A new folder will be created for episodes</p>
</div>
</a>
<br/><br/>
<a href="$sbRoot/home/addShows/trendingShows/" id="btnNewShow" class="btn btn-large">
<div class="button"><div class="icon-addtrendingshow"></div></div>
<div class="buttontext">
<h3>Add Trending Show</h3>
<p>For shows that you haven't downloaded yet, this option lets you choose from a list of current trending shows with ratings to add, creates a directory for its episodes, and adds it to SickGear.</p>
</div>
</a>
<a class="btn btn-large" href="$sbRoot/home/addShows/trendingShows/">
<div class="button"><div class="icon-addtrendingshow"></div></div>
<div class="buttontext">
<h3>Add From Trending</h3>
<p>Browse a current trending show list to add from. A folder for episodes will be created</p>
</div>
</a>
<br/><br/>
#if $sickbeard.USE_TRAKT == True:
<a href="$sbRoot/home/addShows/recommendedShows/" id="btnNewShow" class="btn btn-large">
<div class="button"><div class="icon-addrecommendedshow"></div></div>
<div class="buttontext">
<h3>Add Recommended Shows</h3>
<p>For shows that you haven't downloaded yet, this option recommends shows to add based on your Trakt.tv show library, creates a directory for its episodes, and adds it to SickGear.</p>
</div>
</a>
<a class="btn btn-large" href="$sbRoot/home/addShows/existingShows/">
<div class="button"><div class="icon-addexistingshow"></div></div>
<div class="buttontext">
<h3>Add Existing Shows</h3>
<p>Scan parent folders for shows and episode metadata to import into SickGear</p>
</div>
</a>
<br/><br/>
#end if
<a href="$sbRoot/home/addShows/existingShows/" id="btnExistingShow" class="btn btn-large">
<div class="button"><div class="icon-addexistingshow"></div></div>
<div class="buttontext">
<h3>Add Existing Shows</h3>
<p>Use this option to add shows that already have a folder created on your hard drive. SickGear will scan your existing metadata/episodes and add the show accordingly.</p>
</div>
</a>
#if True == $sickbeard.USE_TRAKT:
<a class="btn btn-large" href="$sbRoot/home/addShows/recommendedShows/">
<div class="button"><div class="icon-addrecommendedshow"></div></div>
<div class="buttontext">
<h3>Add Recommended</h3>
<p>Browse recommendations based on your Trakt.tv show library to add to SickGear</p>
</div>
</a>
#else
<div class="buttontext" style="padding:10px 5px">
<p>There's more... unlock another button to browse<br />
recommended shows based on your Trakt.tv show<br />
library by enabling Trakt in Config/Notifications/Social</p>
</div>
#end if
</div>
</div>
<div style="clear:both">&nbsp;</div>
#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl")
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -2,10 +2,10 @@
#from sickbeard.helpers import anon_url
<table id="addRootDirTable" class="sickbeardTable tablesorter">
<thead><tr><th class="col-checkbox"><input type="checkbox" id="checkAll" checked=checked></th><th>Directory</th><th width="20%">Show Name (tvshow.nfo)<th width="20%">Indexer</td></tr></thead>
<thead><tr><th class="col-checkbox"><input type="checkbox" id="checkAll" checked=checked></th><th>Parent\show folder</th><th width="20%">Show name<br />(tvshow.nfo)<th width="15%">TV database</td></tr></thead>
<tfoot>
<tr>
<th rowspan="1" colspan="4" align="left"><a href="#" style="padding-left: 10px;" class="showManage">Manage Directories</a></th>
<th rowspan="1" colspan="4" align="left"><a href="#" class="showManage">Manage Directories</a></th>
</tr>
</tfoot>
<tbody>
@ -31,7 +31,7 @@
<td class="col-checkbox"><input type="checkbox" id="$show_id" class="dirCheck" checked=checked></td>
<td><label for="$show_id">$curDir['display_dir']</label></td>
#if $curDir['existing_info'][1] and $indexer > 0:
<td><a href="<%= anon_url(sickbeard.indexerApi(indexer).config['show_url'], curDir['existing_info'][0]) %>">$curDir['existing_info'][1]</a></td>
<td><a href="<%= anon_url(sickbeard.indexerApi(indexer).config['show_url'], curDir['existing_info'][0]) %>" target="_new">$curDir['existing_info'][1]</a></td>
#else:
<td>?</td>
#end if

View file

@ -24,15 +24,17 @@
<h1 class="title">$title</h1>
#end if
<image class="preload-image" style="position:absolute;top:-999px" src="$sbRoot/images/loading32<%= '-dark' if 'dark' == sickbeard.THEME_NAME else '' %>.gif" width="32" height="32" border="0">
<div id="newShowPortal">
<div id="displayText">aoeu</div>
<br />
<form id="addShowForm" method="post" action="$sbRoot/home/addShows/addNewShow" accept-charset="utf-8" style="width: 800px;">
<form id="addShowForm" method="post" action="$sbRoot/home/addShows/addNewShow" accept-charset="utf-8">
<fieldset class="sectionwrap">
<legend class="legendStep">Find a show on the TVDB or TVRAGE</legend>
<fieldset class="sectionwrap step-one">
<legend class="legendStep"><p>Find show at a TV database</p></legend>
<div class="stepDiv">
<input type="hidden" id="indexer_timeout" value="$sickbeard.INDEXER_TIMEOUT" />
@ -48,10 +50,10 @@
#else
<input type="text" id="nameToSearch" value="$default_show_name" class="form-control form-control-inline input-sm input350" />
&nbsp;
<span style="float:right">
<select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm">
<option value="en" selected="selected">en</option>
</select><b>*</b>
&nbsp;
</select><b>&nbsp;*</b>
<select name="providedIndexer" id="providedIndexer" class="form-control form-control-inline input-sm">
<option value="0" #if $provided_indexer == 0 then "selected=\"selected\"" else ""#>All Indexers</option>
#for $indexer in $indexers
@ -60,21 +62,21 @@
</select>
&nbsp;
<input class="btn btn-inline" type="button" id="searchName" value="Search" />
</span>
<br />
<p style="margin:5px 0 15px"><b>*</b> SickGear supports english episodes. The language choice is used for fetching metadata and episode filenames</p>
<br />
<b>*</b> This will only affect the language of the retrieved metadata file contents and episode filenames.<br />
This <b>DOES NOT</b> allow SickGear to download non-english TV episodes!<br />
<br />
<div id="searchResults" style="height: 100%;"><br/></div>
<div id="searchResults" style="height: 100%"></div>
#end if
</div>
<div style="clear:both">&nbsp;</div>
</fieldset>
<fieldset class="sectionwrap">
<legend class="legendStep">Pick the parent folder</legend>
<fieldset class="sectionwrap step-two">
<legend class="legendStep"><p>Pick parent folder</p></legend>
<div class="stepDiv">
<div class="stepDiv parent-folder">
#if $provided_show_dir
Pre-chosen Destination Folder: <b>$provided_show_dir</b> <br />
<input type="hidden" id="fullShowPath" name="fullShowPath" value="$provided_show_dir" /><br />
@ -82,13 +84,15 @@
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_rootDirs.tmpl')
#end if
</div>
<div style="clear:both">&nbsp;</div>
</fieldset>
<fieldset class="sectionwrap">
<legend class="legendStep">Customize options</legend>
<fieldset class="sectionwrap step-three">
<legend class="legendStep"><p>Set custom options</p></legend>
<div class="stepDiv">
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_addShowOptions.tmpl')
</div>
<div style="clear:both">&nbsp;</div>
</fieldset>
#for $curNextDir in $other_shows

View file

@ -12,7 +12,6 @@
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
<link rel="stylesheet" type="text/css" href="$sbRoot/css/formwizard.css?$sbPID" />
<script type="text/javascript" src="$sbRoot/js/formwizard.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/recommendedShows.js?$sbPID"></script>
@ -24,35 +23,40 @@
<h1 class="title">$title</h1>
#end if
<image class="preload-image" style="position:absolute;top:-999px" src="$sbRoot/images/loading32<%= '-dark' if 'dark' == sickbeard.THEME_NAME else '' %>.gif" width="32" height="32" border="0">
<div id="newShowPortal">
<div id="displayText"></div>
<br />
<form id="recommendedShowsForm" method="post" action="$sbRoot/home/addShows/addRecommendedShow" accept-charset="utf-8" style="width: 800px;">
<form id="addShowForm" method="post" action="$sbRoot/home/addShows/addRecommendedShow" accept-charset="utf-8">
<fieldset class="sectionwrap">
<legend class="legendStep">Select a recommended show</legend>
<fieldset class="sectionwrap step-one">
<legend class="legendStep"><p>Select a recommended show</p></legend>
<div class="stepDiv">
<div id="searchResults" style="height: 100%;"><br/></div>
</div>
<div style="clear:both">&nbsp;</div>
</fieldset>
<fieldset class="sectionwrap">
<legend class="legendStep">Pick the parent folder</legend>
<fieldset class="sectionwrap step-two">
<legend class="legendStep"><p>Pick parent folder</p></legend>
<div class="stepDiv">
<div class="stepDiv parent-folder">
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_rootDirs.tmpl")
</div>
<div style="clear:both">&nbsp;</div>
</fieldset>
<fieldset class="sectionwrap">
<legend class="legendStep">Customize options</legend>
<fieldset class="sectionwrap step-three">
<legend class="legendStep"><p>Set custom options</p></legend>
<div class="stepDiv">
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_addShowOptions.tmpl")
</div>
<div style="clear:both">&nbsp;</div>
</fieldset>
</form>

View file

@ -2,62 +2,77 @@
#from sickbeard.common import *
#from sickbeard import subtitles
#if $sickbeard.USE_SUBTITLES:
<div class="field-pair alt">
<input type="checkbox" name="subtitles" id="subtitles" #if $sickbeard.SUBTITLES_DEFAULT then "checked=\"checked\"" else ""# />
<label for="subtitles" class="clearfix">
<span class="component-title">Subtitles</span>
<span class="component-desc">Download subtitles for this show?</span>
</label>
</div>
#end if
<div class="field-pair">
<label for="statusSelect" class="nocheck clearfix">
<span class="component-title">
<select name="defaultStatus" id="statusSelect" class="form-control form-control-inline input-sm">
#for $curStatus in [$SKIPPED, $WANTED, $ARCHIVED, $IGNORED]:
<option value="$curStatus" #if $sickbeard.STATUS_DEFAULT == $curStatus then 'selected="selected"' else ''#>$statusStrings[$curStatus]</option>
#end for
</select>
</span>
<span class="component-desc">Set the initial status of missing episodes</span>
</label>
</div>
<div class="field-pair alt">
<input class="cb" type="checkbox" name="flatten_folders" id="flatten_folders" #if $sickbeard.FLATTEN_FOLDERS_DEFAULT then "checked=\"checked\"" else ""# />
<label for="flatten_folders" class="clearfix">
<span class="component-title">Flatten Folders</span>
<span class="component-desc">Disregard sub-folders?</span>
</label>
</div>
<div class="field-pair">
<label for="statusSelect">
<span class="component-title input">Initial episode status</span>
<span class="component-desc">
<select name="defaultStatus" id="statusSelect" class="form-control form-control-inline input-sm">
#for $curStatus in [$SKIPPED, $WANTED, $ARCHIVED, $IGNORED]:
<option value="$curStatus" #if $sickbeard.STATUS_DEFAULT == $curStatus then 'selected="selected"' else ''#>$statusStrings[$curStatus]</option>
#end for
</select>
<span>set the initial status of missing episodes</span>
</span>
</label>
</div>
<div class="field-pair alt">
<input type="checkbox" name="anime" id="anime" #if $sickbeard.ANIME_DEFAULT then "checked=\"checked\"" else ""# />
<label for="anime" class="clearfix">
<span class="component-title">Anime</span>
<span class="component-desc">Is this show an Anime?</span>
</label>
</div>
<p class="grey-text">Tip: The following options are <span style="font-weight:800">edit</span>able later in the detail view of the show</p>
</div>
<div class="field-pair alt">
<input type="checkbox" name="scene" id="scene" #if $sickbeard.SCENE_DEFAULT then "checked=\"checked\"" else ""# />
<label for="scene" class="clearfix">
<span class="component-title">Scene Numbering</span>
<span class="component-desc">Is this show scene numbered?</span>
</label>
</div>
#if $sickbeard.USE_SUBTITLES:
<div class="field-pair alt">
<label for="subtitles">
<span class="component-title">Subtitles</span>
<span class="component-desc">
<input type="checkbox" name="subtitles" id="subtitles" #if $sickbeard.SUBTITLES_DEFAULT then "checked=\"checked\"" else ""# />
<p>download subtitles for this show</p>
</span>
</label>
</div>
#end if
#set $qualities = $Quality.splitQuality($sickbeard.QUALITY_DEFAULT)
#set global $anyQualities = $qualities[0]
#set global $bestQualities = $qualities[1]
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_qualityChooser.tmpl")
<br>
<div class="field-pair alt">
<label for="saveDefaultsButton" class="nocheck clearfix">
<span class="component-title"><input class="btn btn-inline" type="button" id="saveDefaultsButton" value="Save Defaults" disabled="disabled" /></span>
<span class="component-desc">Persist current values as the defaults</span>
</label>
</div>
<div class="field-pair alt">
<label for="flatten_folders">
<span class="component-title">Flatten folders</span>
<span class="component-desc">
<input class="cb" type="checkbox" name="flatten_folders" id="flatten_folders" #if $sickbeard.FLATTEN_FOLDERS_DEFAULT then "checked=\"checked\"" else ""# />
<p>do not create sub folders</p>
</span>
</label>
</div>
<div class="field-pair alt">
<label for="anime">
<span class="component-title">Anime</span>
<span class="component-desc">
<input type="checkbox" name="anime" id="anime" #if $sickbeard.ANIME_DEFAULT then "checked=\"checked\"" else ""# />
<p>use anime processing for this show</p>
</span>
</label>
</div>
<div class="field-pair alt">
<label for="scene">
<span class="component-title">Scene numbering</span>
<span class="component-desc">
<input type="checkbox" name="scene" id="scene" #if $sickbeard.SCENE_DEFAULT then "checked=\"checked\"" else ""# />
<p>enable if episodes are numbered by scene releases and not by the TV network</p>
</span>
</label>
</div>
#set $qualities = $Quality.splitQuality($sickbeard.QUALITY_DEFAULT)
#set global $anyQualities = $qualities[0]
#set global $bestQualities = $qualities[1]
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_qualityChooser.tmpl")
<div class="field-pair alt" style="margin-top:30px">
<label for="saveDefaultsButton">
<span class="component-title">Save options as defaults</span>
<span class="component-desc">
<input class="btn btn-inline" type="button" id="saveDefaultsButton" value="Save Defaults" disabled="disabled" />
<p>reuse the above options when adding more shows</p>
</span>
</label>
</div>

View file

@ -38,9 +38,30 @@
#set $ep_downloaded = 0
#set $ep_total = 0
#end if
#try
#set $localRoot = $sbRoot
#except NotFound
#set $localRoot = ''
#end try
#try
#set $localheader = $header
#except NotFound
#set $localheader = ''
#end try
<span class="footerhighlight">$shows_total</span> shows (<span class="footerhighlight">$shows_active</span> active)
| <span class="footerhighlight"><%= ep_downloaded %></span><%= ('', ' (<span class="footerhighlight">+%s</span> snatched)' % str(ep_snatched))[ep_snatched > 0] %>&nbsp;/&nbsp;<span class="footerhighlight">$ep_total</span> episodes downloaded
| <span class="footerhighlight"><%= ep_downloaded %></span>
<%= (
'',\
' (<span class="footerhighlight">+%s</span> snatched)' % \
(
str(ep_snatched),
'<a href="%s/manage/episodeStatuses?whichStatus=2" title="View overview of snatched episodes">%s</a>' % \
(localRoot, str(ep_snatched))
)['Episode Overview' != localheader]
)[0 < ep_snatched]
%>
&nbsp;/&nbsp;<span class="footerhighlight">$ep_total</span> episodes downloaded
| daily search: <span class="footerhighlight"><%= str(sickbeard.dailySearchScheduler.timeLeft()).split('.')[0] %></span>
| backlog search: <span class="footerhighlight">$sbdatetime.sbdatetime.sbfdate($sickbeard.backlogSearchScheduler.nextRun())</span>

View file

@ -2,46 +2,48 @@
#from sickbeard.common import Quality, qualityPresets, qualityPresetStrings
<div class="field-pair">
<label for="qualityPreset" class="nocheck clearfix">
<label for="qualityPreset" class="clearfix">
#set $overall_quality = $Quality.combineQualities($anyQualities, $bestQualities)
<span class="component-title">
<span class="component-title input">Quality</span>
<span class="component-desc">
#set $selected = None
<select id="qualityPreset" class="form-control form-control-inline input-sm">
<option value="0">Custom</option>
<select id="qualityPreset" class="form-control form-control-inline input-sm">
<option value="0">Custom</option>
#for $curPreset in sorted($qualityPresets):
<option value="$curPreset" #if $curPreset == $overall_quality then "selected=\"selected\"" else ""# #if $qualityPresetStrings[$curPreset].endswith("0p") then "style=\"padding-left: 15px;\"" else ""#>$qualityPresetStrings[$curPreset]</option>
<option value="$curPreset" #if $curPreset == $overall_quality then "selected=\"selected\"" else ""# #if $qualityPresetStrings[$curPreset].endswith("0p") then "style=\"padding-left: 15px;\"" else ""#>$qualityPresetStrings[$curPreset]</option>
#end for
</select>
</span>
<span class="component-desc">Preferred quality of episodes to be download</span>
</label>
</select>
<span>preferred episode quality to download</span>
</span>
</label>
</div>
<div id="customQualityWrapper">
<div id="customQuality">
<div class="component-group-desc">
<p>One of the <b>Initial</b> quality selections must be obtained before SickGear will attempt to process the <b>Archive</b> selections.</p>
</div>
<div class="component-group-desc">
<p>One of the <em>Initial</em> quality selections must succeed before attempting to process <em>Archive</em> selections.</p>
</div>
<div style="padding-right: 40px; text-align: center;" class="float-left">
<h4>Initial</h4>
#set $anyQualityList = filter(lambda x: x > $Quality.NONE, $Quality.qualityStrings)
<select id="anyQualities" name="anyQualities" multiple="multiple" size="$len($anyQualityList)" class="form-control form-control-inline input-sm">
#for $curQuality in sorted($anyQualityList):
<option value="$curQuality" #if $curQuality in $anyQualities then "selected=\"selected\"" else ""#>$Quality.qualityStrings[$curQuality]</option>
#end for
</select>
</div>
<span class="component-desc">
<div style="float:left; padding-right: 40px">
<h4>Initial</h4>
#set $anyQualityList = filter(lambda x: x > $Quality.NONE, $Quality.qualityStrings)
<select id="anyQualities" name="anyQualities" multiple="multiple" size="$len($anyQualityList)" class="form-control form-control-inline input-sm">
#for $curQuality in sorted($anyQualityList):
<option value="$curQuality" #if $curQuality in $anyQualities then "selected=\"selected\"" else ""#>$Quality.qualityStrings[$curQuality]</option>
#end for
</select>
</div>
<div style="text-align: center;" class="float-left">
<h4>Archive</h4>
#set $bestQualityList = filter(lambda x: x > $Quality.SDTV and x < $Quality.UNKNOWN, $Quality.qualityStrings)
<select id="bestQualities" name="bestQualities" multiple="multiple" size="$len($bestQualityList)" class="form-control form-control-inline input-sm">
#for $curQuality in sorted($bestQualityList):
<option value="$curQuality" #if $curQuality in $bestQualities then "selected=\"selected\"" else ""#>$Quality.qualityStrings[$curQuality]</option>
#end for
</select>
</div>
</div>
<div style="float:left">
<h4>Archive</h4>
#set $bestQualityList = filter(lambda x: x > $Quality.SDTV and x < $Quality.UNKNOWN, $Quality.qualityStrings)
<select id="bestQualities" name="bestQualities" multiple="multiple" size="$len($bestQualityList)" class="form-control form-control-inline input-sm">
#for $curQuality in sorted($bestQualityList):
<option value="$curQuality" #if $curQuality in $bestQualities then "selected=\"selected\"" else ""#>$Quality.qualityStrings[$curQuality]</option>
#end for
</select>
</div>
</span>
</div>
</div>

View file

@ -7,7 +7,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SickGear - BRANCH:[$sickbeard.BRANCH] - $title</title>
<!--[if lt IE 9]>
@ -37,7 +37,7 @@
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/bootstrap.css?$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/browser.css?$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/jquery-ui-1.10.4.custom.css?$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/jquery.qtip-2.0.1.min.css?$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/jquery.qtip-2.2.1.min.css?$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/pnotify.custom.min.css?$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/style.css?$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/${sickbeard.THEME_NAME}.css?$sbPID" />
@ -52,7 +52,7 @@
<script type="text/javascript" src="$sbRoot/js/lib/jquery.selectboxes.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.tablesorter-2.17.7.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.tablesorter.widgets-2.17.7.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.qtip-2.0.1.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.qtip-2.2.1.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/pnotify.custom.min.js"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.form-3.35.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.ui.touch-punch-0.2.2.min.js?$sbPID"></script>
@ -61,10 +61,10 @@
<script type="text/javascript" src="$sbRoot/js/script.js?$sbPID"></script>
#if $sickbeard.FUZZY_DATING:
#if $sickbeard.FUZZY_DATING:
<script type="text/javascript" src="$sbRoot/js/moment/moment.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/fuzzyMoment.js?$sbPID"></script>
#end if
#end if
<script type="text/javascript" charset="utf-8">
<!--
sbRoot = '$sbRoot'; // needed for browser.js & ajaxNotifications.js
@ -98,7 +98,6 @@
\$("#SubMenu a:contains('Anime')").addClass('btn').html('<span class="submenu-icon-anime pull-left"></span> Anime');
\$("#SubMenu a:contains('Settings')").addClass('btn').html('<span class="ui-icon ui-icon-search pull-left"></span> Search Settings');
\$("#SubMenu a:contains('Provider')").addClass('btn').html('<span class="ui-icon ui-icon-search pull-left"></span> Search Providers');
\$("#SubMenu a:contains('Backup/Restore')").addClass('btn').html('<span class="ui-icon ui-icon-gear pull-left"></span> Backup/Restore');
\$("#SubMenu a:contains('General')").addClass('btn').html('<span class="ui-icon ui-icon-gear pull-left"></span> General');
\$("#SubMenu a:contains('Episode Status')").addClass('btn').html('<span class="ui-icon ui-icon-transferthick-e-w pull-left"></span> Episode Status Management');
\$("#SubMenu a:contains('Missed Subtitle')").addClass('btn').html('<span class="ui-icon ui-icon-transferthick-e-w pull-left"></span> Missed Subtitles');
@ -119,13 +118,13 @@
\$("#NAV$topmenu").addClass("active");
\$('.dropdown-toggle').dropdownHover();
});
//-->
</script>
<script type="text/javascript" src="$sbRoot/js/confirmations.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/confirmations.js?$sbPID"></script>
</head>
#set $tab = 4
<body>
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
@ -136,84 +135,83 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="$sbRoot/home/" title="SickGear"><img alt="SickGear" src="$sbRoot/images/sickgear.png" style="height: 50px;" class="img-responsive pull-left" /></a>
<a href="$sbRoot/home/" class="navbar-brand" tabindex="-1" title="SickGear"><img alt="SickGear" src="$sbRoot/images/sickgear.png" style="height: 50px;" class="img-responsive pull-left" /></a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li id="NAVhome" class="dropdown">
<a href="$sbRoot/home/" class="dropdown-toggle" data-toggle="dropdown">Shows <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/home/"><i class="menu-icon-home"></i>&nbsp;Show List</a></li>
<li><a href="$sbRoot/home/addShows/"><i class="menu-icon-addshow"></i>&nbsp;Add Shows</a></li>
<li><a href="$sbRoot/home/postprocess/"><i class="menu-icon-postprocess"></i>&nbsp;Manual Post-Processing</a></li>
</ul>
</li>
<li id="NAVcomingEpisodes">
<a href="$sbRoot/comingEpisodes/">Coming Episodes</a>
</li>
<li id="NAVhistory">
<a href="$sbRoot/history/">History</a>
</li>
<li id="NAVmanage" class="dropdown">
<a href="$sbRoot/manage/" class="dropdown-toggle" data-toggle="dropdown">Manage <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/manage/"><i class="menu-icon-manage"></i>&nbsp;Mass Update</a></li>
<li><a href="$sbRoot/manage/backlogOverview/"><i class="menu-icon-backlog-view"></i>&nbsp;Backlog Overview</a></li>
<li><a href="$sbRoot/manage/manageSearches/"><i class="menu-icon-manage-searches"></i>&nbsp;Manage Searches</a></li>
<li><a href="$sbRoot/manage/episodeStatuses/"><i class="menu-icon-backlog"></i>&nbsp;Episode Status Management</a></li>
#if $sickbeard.USE_PLEX and $sickbeard.PLEX_SERVER_HOST != "":
<li><a href="$sbRoot/home/updatePLEX/"><i class="menu-icon-backlog-view"></i>&nbsp;Update PLEX</a></li>
#end if
#if $sickbeard.USE_XBMC and $sickbeard.XBMC_HOST != "":
<li><a href="$sbRoot/home/updateXBMC/"><i class="menu-icon-xbmc"></i>&nbsp;Update XBMC</a></li>
#end if
#if $sickbeard.USE_TORRENTS and $sickbeard.TORRENT_METHOD != 'blackhole' \
and ($sickbeard.ENABLE_HTTPS and $sickbeard.TORRENT_HOST[:5] == 'https' \
or not $sickbeard.ENABLE_HTTPS and $sickbeard.TORRENT_HOST[:5] == 'http:'):
<li><a href="$sbRoot/manage/manageTorrents/"><i class="menu-icon-bittorrent"></i>&nbsp;Manage Torrents</a></li>
#end if
#if $sickbeard.USE_FAILED_DOWNLOADS:
<li><a href="$sbRoot/manage/failedDownloads/"><i class="menu-icon-failed-download"></i>&nbsp;Failed Downloads</a></li>
#end if
#if $sickbeard.USE_SUBTITLES:
<li><a href="$sbRoot/manage/subtitleMissed/"><i class="menu-icon-backlog"></i>&nbsp;Missed Subtitle Management</a></li>
#end if
</ul>
</li>
<li id="NAVerrorlogs" class="dropdown">
<a href="$sbRoot/errorlogs/" class="dropdown-toggle" data-toggle="dropdown">$logPageTitle <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/errorlogs/"><i class="menu-icon-viewlog-errors"></i>&nbsp;View Log (Errors)</a></li>
<li><a href="$sbRoot/errorlogs/viewlog/"><i class="menu-icon-viewlog"></i>&nbsp;View Log</a></li>
</ul>
</li>
<li id="NAVconfig" class="dropdown">
<a href="$sbRoot/config/" class="dropdown-toggle" data-toggle="dropdown"><img src="$sbRoot/images/menu/system18.png" class="navbaricon hidden-xs" /><b class="caret hidden-xs"></b><span class="visible-xs">Config <b class="caret"></b></span></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/config/"><i class="menu-icon-help"></i>&nbsp;Help &amp; Info</a></li>
<li><a href="$sbRoot/config/general/"><i class="menu-icon-config"></i>&nbsp;General</a></li>
<li><a href="$sbRoot/config/backuprestore/"><i class="menu-icon-config"></i>&nbsp;Backup &amp; Restore</a></li>
<li><a href="$sbRoot/config/search/"><i class="menu-icon-config"></i>&nbsp;Search Settings</a></li>
<li><a href="$sbRoot/config/providers/"><i class="menu-icon-config"></i>&nbsp;Search Providers</a></li>
<li><a href="$sbRoot/config/subtitles/"><i class="menu-icon-config"></i>&nbsp;Subtitles Settings</a></li>
<li><a href="$sbRoot/config/postProcessing/"><i class="menu-icon-config"></i>&nbsp;Post Processing</a></li>
<li><a href="$sbRoot/config/notifications/"><i class="menu-icon-config"></i>&nbsp;Notifications</a></li>
<li><a href="$sbRoot/config/anime/"><i class="menu-icon-config"></i>&nbsp;Anime</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="$sbRoot/images/menu/system18-2.png" class="navbaricon hidden-xs" /><b class="caret hidden-xs"></b><span class="visible-xs">System <b class="caret"></b></span></a>
<a href="$sbRoot/home/" class="dropdown-toggle" data-toggle="dropdown" tabindex="$tab#set $tab += 1#">Shows <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/manage/manageSearches/forceVersionCheck"><i class="menu-icon-update"></i>&nbsp;Force Version Check</a></li>
<li><a href="$sbRoot/home/restart/?pid=$sbPID" class="confirm restart"><i class="menu-icon-restart"></i>&nbsp;Restart</a></li>
<li><a href="$sbRoot/home/shutdown/?pid=$sbPID" class="confirm shutdown"><i class="menu-icon-shutdown"></i>&nbsp;Shutdown</a></li>
<li><a href="$sbRoot/home/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-home"></i>&nbsp;Show List</a></li>
<li><a href="$sbRoot/home/addShows/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-addshow"></i>&nbsp;Add Shows</a></li>
<li><a href="$sbRoot/home/postprocess/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-postprocess"></i>&nbsp;Manual Post-Processing</a></li>
</ul>
</li>
<li id="NAVcomingEpisodes">
<a href="$sbRoot/comingEpisodes/" tabindex="$tab#set $tab += 1#">Coming Episodes</a>
</li>
<li id="NAVhistory">
<a href="$sbRoot/history/" tabindex="$tab#set $tab += 1#">History</a>
</li>
<li id="NAVmanage" class="dropdown">
<a href="$sbRoot/manage/" class="dropdown-toggle" data-toggle="dropdown" tabindex="$tab#set $tab += 1#">Manage <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/manage/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-manage"></i>&nbsp;Mass Update</a></li>
<li><a href="$sbRoot/manage/backlogOverview/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-backlog-view"></i>&nbsp;Backlog Overview</a></li>
<li><a href="$sbRoot/manage/manageSearches/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-manage-searches"></i>&nbsp;Manage Searches</a></li>
<li><a href="$sbRoot/manage/episodeStatuses/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-backlog"></i>&nbsp;Episode Status Management</a></li>
#if $sickbeard.USE_PLEX and $sickbeard.PLEX_SERVER_HOST != "":
<li><a href="$sbRoot/home/updatePLEX/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-backlog-view"></i>&nbsp;Update PLEX</a></li>
#end if
#if $sickbeard.USE_XBMC and $sickbeard.XBMC_HOST != "":
<li><a href="$sbRoot/home/updateXBMC/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-xbmc"></i>&nbsp;Update XBMC</a></li>
#end if
#if $sickbeard.USE_TORRENTS and $sickbeard.TORRENT_METHOD != 'blackhole' \
and ($sickbeard.ENABLE_HTTPS and $sickbeard.TORRENT_HOST[:5] == 'https' \
or not $sickbeard.ENABLE_HTTPS and $sickbeard.TORRENT_HOST[:5] == 'http:'):
<li><a href="$sbRoot/manage/manageTorrents/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-bittorrent"></i>&nbsp;Manage Torrents</a></li>
#end if
#if $sickbeard.USE_FAILED_DOWNLOADS:
<li><a href="$sbRoot/manage/failedDownloads/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-failed-download"></i>&nbsp;Failed Downloads</a></li>
#end if
#if $sickbeard.USE_SUBTITLES:
<li><a href="$sbRoot/manage/subtitleMissed/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-backlog"></i>&nbsp;Missed Subtitle Management</a></li>
#end if
</ul>
</li>
<li id="NAVerrorlogs" class="dropdown">
<a href="$sbRoot/errorlogs/" class="dropdown-toggle" data-toggle="dropdown" tabindex="$tab#set $tab += 1#">$logPageTitle <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/errorlogs/"><i class="menu-icon-viewlog-errors" tabindex="$tab#set $tab += 1#"></i>&nbsp;View Log (Errors)</a></li>
<li><a href="$sbRoot/errorlogs/viewlog/"><i class="menu-icon-viewlog" tabindex="$tab#set $tab += 1#"></i>&nbsp;View Log</a></li>
</ul>
</li>
<li id="NAVconfig" class="dropdown">
<a href="$sbRoot/config/" class="dropdown-toggle" data-toggle="dropdown" tabindex="$tab#set $tab += 1#"><img src="$sbRoot/images/menu/system18.png" class="navbaricon hidden-xs" /><b class="caret hidden-xs"></b><span class="visible-xs">Config <b class="caret"></b></span></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/config/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-help"></i>&nbsp;Help &amp; Info</a></li>
<li><a href="$sbRoot/config/general/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-config"></i>&nbsp;General</a></li>
<li><a href="$sbRoot/config/search/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-config"></i>&nbsp;Search Settings</a></li>
<li><a href="$sbRoot/config/providers/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-config"></i>&nbsp;Search Providers</a></li>
<li><a href="$sbRoot/config/subtitles/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-config"></i>&nbsp;Subtitles Settings</a></li>
<li><a href="$sbRoot/config/postProcessing/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-config"></i>&nbsp;Post Processing</a></li>
<li><a href="$sbRoot/config/notifications/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-config"></i>&nbsp;Notifications</a></li>
<li><a href="$sbRoot/config/anime/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-config"></i>&nbsp;Anime</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" tabindex="$tab#set $tab += 1#"><img src="$sbRoot/images/menu/system18-2.png" class="navbaricon hidden-xs" /><b class="caret hidden-xs"></b><span class="visible-xs">System <b class="caret"></b></span></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/manage/manageSearches/forceVersionCheck" tabindex="$tab#set $tab += 1#"><i class="menu-icon-update"></i>&nbsp;Force Version Check</a></li>
<li><a href="$sbRoot/home/restart/?pid=$sbPID" class="confirm restart" tabindex="$tab#set $tab += 1#"><i class="menu-icon-restart"></i>&nbsp;Restart</a></li>
<li><a href="$sbRoot/home/shutdown/?pid=$sbPID" class="confirm shutdown" tabindex="$tab#set $tab += 1#"><i class="menu-icon-shutdown"></i>&nbsp;Shutdown</a></li>
</ul>
</li>
</ul>
@ -222,35 +220,47 @@
</div><!-- /.container-fluid -->
</nav>
#if $varExists('submenu'):
#if $varExists('submenu'):
<div id="SubMenu">
<span>
#set $first = True
#for $menuItem in $submenu:
#if 'requires' not in $menuItem or $menuItem.requires():
#if type($menuItem.path) == dict:
#if $first then "" else "</span><span>"#<b>$menuItem.title</b>
#set $first = False
#set $inner_first = True
#for $cur_link in $menuItem.path:
#if $inner_first then "" else "&middot; "#<a class="inner" href="$sbRoot/$menuItem.path[$cur_link]">$cur_link</a>
#set $inner_first = False
#end for
#else
#if $first then "" else ""#<a href="$sbRoot/$menuItem.path" #if 'confirm' in $menuItem then "class=\"confirm\"" else "" #>$menuItem.title</a>
#set $first = False
#end if
#end if
#end for
</span>
<span>
#set $first = True
#for $menuItem in $submenu:
#if 'requires' not in $menuItem or $menuItem.requires():
#if type($menuItem.path) == dict:
#if $first then "" else "</span><span>"#<b>$menuItem.title</b>
#set $first = False
#set $inner_first = True
#for $cur_link in $menuItem.path:
#if $inner_first then "" else "&middot; "#<a class="inner" href="$sbRoot/$menuItem.path[$cur_link]">$cur_link</a>
#set $inner_first = False
#end for
#else
#if $first then "" else ""#<a href="$sbRoot/$menuItem.path" #if 'confirm' in $menuItem then "class=\"confirm\"" else "" # tabindex="$tab#set $tab += 1#">$menuItem.title</a>
#set $first = False
#end if
#end if
#end for
</span>
</div>
#end if
#if $sickbeard.NEWEST_VERSION_STRING:
#end if
#if $sickbeard.NEWEST_VERSION_STRING:
<div class="alert alert-success upgrade-notification" role="alert">
<span>$sickbeard.NEWEST_VERSION_STRING</span>
</div>
#end if
#end if
<div id="contentWrapper">
<div id="content">
#set $items = []
#try
$items.append($topmenu)
#except (NameError, NotFound):
#pass
#end try
#try
$items.append($layout)
#except (NameError, NotFound):
#pass
#end try
#set $page_class = ('', ' class="%s"' % '_'.join($items).lower().replace(' ', '-'))[0 < len($items)]
<div id="contentWrapper">
<div id="content"$page_class>

View file

@ -40,7 +40,7 @@ Manage episodes with status <select name="whichStatus" class="form-control form-
<form action="$sbRoot/manage/changeEpisodeStatuses" method="post">
<input type="hidden" id="oldStatus" name="oldStatus" value="$whichStatus" />
<h2>Shows containing $common.statusStrings[$int($whichStatus)] episodes</h2>
<h2><span class="grey-text">Shows containing</span> $common.statusStrings[$int($whichStatus)] <span class="grey-text">episodes</span></h2>
<br />
@ -65,7 +65,7 @@ $statusList.append($common.FAILED)
<option value="$curStatus">$common.statusStrings[$curStatus]</option>
#end for
</select>
<input class="btn btn-inline" type="submit" value="Go" />
<input class="btn btn-inline go" type="submit" value="Go" />
<div>
<button type="button" class="btn btn-xs selectAllShows">Select all</a></button>
@ -76,7 +76,7 @@ $statusList.append($common.FAILED)
<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0">
#for $cur_indexer_id in $sorted_show_ids:
<tr id="$cur_indexer_id">
<th><input type="checkbox" class="allCheck" id="allCheck-$cur_indexer_id" name="$cur_indexer_id-all" checked="checked" /></th>
<th><input type="checkbox" class="allCheck" id="allCheck-$cur_indexer_id" name="$cur_indexer_id-all" /></th>
<th colspan="2" style="width: 100%; text-align: left;"><a class="whitelink" href="$sbRoot/home/displayShow?show=$cur_indexer_id">$show_names[$cur_indexer_id]</a> ($ep_counts[$cur_indexer_id]) <input type="button" class="pull-right get_more_eps btn" id="$cur_indexer_id" value="Expand" /></th>
</tr>
#end for

View file

@ -1,78 +1,93 @@
$(document).ready(function() {
$(document).ready(function(){
$('#checkAll').live('click', function(){
var seasCheck = this;
var tableDiv = $('#tableDiv');
$('.dirCheck').each(function(){
this.checked = seasCheck.checked;
});
});
tableDiv.on('click', '#checkAll', function(){
$('#submitShowDirs').click(function(){
var cbToggle = this.checked;
var dirArr = new Array();
$('.dirCheck').each(function(){
this.checked = cbToggle;
});
});
$('.dirCheck').each(function(i,w) {
if (this.checked == true) {
var show = $(this).attr('id');
var indexer = $(this).closest('tr').find('select').val();
dirArr.push(encodeURIComponent(indexer + '|' + show));
}
});
$('#submitShowDirs').click(function(){
if (dirArr.length == 0)
return false;
var dirArr = [];
url = sbRoot+'/home/addShows/addExistingShows?promptForSettings='+ ($('#promptForSettings').prop('checked') ? 'on' : 'off');
$('.dirCheck').each(function(){
if (true == this.checked){
var show = $(this).attr('id');
var indexer = $(this).closest('tr').find('select').val();
dirArr.push(encodeURIComponent(indexer + '|' + show));
}
});
url += '&shows_to_add='+dirArr.join('&shows_to_add=');
if (0 == dirArr.length)
return false;
window.location.href = url;
});
window.location.href = sbRoot + '/home/addShows/addExistingShows'
+ '?promptForSettings=' + ($('#promptForSettings').prop('checked') ? 'on' : 'off')
+ '&shows_to_add=' + dirArr.join('&shows_to_add=');
});
function loadContent() {
var url = '';
$('.dir_check').each(function(i,w){
if ($(w).is(':checked')) {
if (url.length)
url += '&';
url += 'rootDir=' + encodeURIComponent($(w).attr('id'));
}
});
function loadContent(){
var url = '';
$('.dir_check').each(function(i, w){
if ($(w).is(':checked')){
url += (url.length ? '&' : '')
+ 'rootDir=' + encodeURIComponent($(w).attr('id'));
}
});
$('#tableDiv').html('<img id="searchingAnim" src="' + sbRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> loading folders...');
$.get(sbRoot+'/home/addShows/massAddTable', url, function(data) {
$('#tableDiv').html(data);
$("#addRootDirTable").tablesorter({
//sortList: [[1,0]],
widgets: ['zebra'],
headers: {
0: { sorter: false }
}
});
});
$('#tableDiv').html('<img id="searchingAnim"'
+ ' style="margin-right:10px"'
+ ' src="' + sbRoot + '/images/loading32' + themeSpinner + '.gif"'
+ ' height="32" width="32" />'
+ ' scanning parent folders...');
}
$.get(sbRoot + '/home/addShows/massAddTable',
url,
function(data){
$('#tableDiv').html(data);
$('#addRootDirTable').tablesorter({
sortList: [[1,0]],
widgets: ['zebra'],
headers: {
0: { sorter: false }
}
});
});
}
var last_txt = '';
$('#rootDirText').change(function() {
if (last_txt == $('#rootDirText').val())
return false;
else
last_txt = $('#rootDirText').val();
$('#rootDirStaticList').html('');
$('#rootDirs option').each(function(i, w) {
$('#rootDirStaticList').append('<li class="ui-state-default ui-corner-all"><input type="checkbox" class="dir_check" id="'+$(w).val()+'" checked=checked> <label for="'+$(w).val()+'" style="color:#09A2FF;"><b>'+$(w).val()+'</b></label></li>')
});
loadContent();
});
$('.dir_check').live('click', loadContent);
var last_txt = '', new_text = '', id;
$('#rootDirText').change(function(){
if (last_txt == (new_text = $('#rootDirText').val()))
return false;
$('.showManage').live('click', function() {
$( "#tabs" ).tabs( 'select', 0 );
});
last_txt = new_text;
$('#rootDirStaticList').html('');
$('#rootDirs').find('option').each(function(i, w){
id = $(w).val();
$('#rootDirStaticList').append('<li class="ui-state-default ui-corner-all">'
+ '<input id="' + id + '" type="checkbox"' + ' checked=checked'
+ ' class="dir_check"'
+ ' />'
+ ' <label for="' + id + '"'
+ ' style="color:#09A2FF">'
+ '<b>' + id + '</b></label>'
+ '</li>')
});
loadContent();
});
$('#rootDirStaticList').on('click', '.dir_check', loadContent);
tableDiv.on('click', '.showManage', function(event) {
event.preventDefault();
$('#tabs').tabs('option', 'active', 0);
$('html,body').animate({scrollTop: 0}, 1000);
});
});

View file

@ -1,24 +0,0 @@
$(document).ready(function(){
var loading = '<img src="' + sbRoot + '/images/loading16' + themeSpinner + '.gif" height="16" width="16" />';
$('#Backup').click(function() {
$("#Backup").attr("disabled", true);
$('#Backup-result').html(loading);
var backupDir = $("#backupDir").val();
$.get(sbRoot + "/config/backup", {'backupDir': backupDir})
.done(function (data) {
$('#Backup-result').html(data);
$("#Backup").attr("disabled", false);
});
});
$('#Restore').click(function() {
$("#Restore").attr("disabled", true);
$('#Restore-result').html(loading);
var backupFile = $("#backupFile").val();
$.get(sbRoot + "/config/restore", {'backupFile': backupFile})
.done(function (data) {
$('#Restore-result').html(data);
$("#Restore").attr("disabled", false);
});
});
});

View file

@ -17,7 +17,7 @@ $(document).ready(function () {
} else {
$('#unpack').qtip('option', {
'content.text': 'Unrar Executable not found.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#unpack').qtip('toggle', true);
$('#unpack').css('background-color', '#FFFFDD');
@ -56,21 +56,21 @@ $(document).ready(function () {
if (data == "invalid") {
$('#naming_pattern').qtip('option', {
'content.text': 'This pattern is invalid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_pattern').qtip('toggle', true);
$('#naming_pattern').css('background-color', '#FFDDDD');
} else if (data == "seasonfolders") {
$('#naming_pattern').qtip('option', {
'content.text': 'This pattern would be invalid without the folders, using it will force "Flatten" off for all shows.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_pattern').qtip('toggle', true);
$('#naming_pattern').css('background-color', '#FFFFDD');
} else {
$('#naming_pattern').qtip('option', {
'content.text': 'This pattern is valid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-green'
'style.classes': 'qtip-green qtip-rounded qtip-shadow'
});
$('#naming_pattern').qtip('toggle', false);
$('#naming_pattern').css('background-color', '#FFFFFF');
@ -97,21 +97,21 @@ $(document).ready(function () {
if (data == "invalid") {
$('#naming_abd_pattern').qtip('option', {
'content.text': 'This pattern is invalid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_abd_pattern').qtip('toggle', true);
$('#naming_abd_pattern').css('background-color', '#FFDDDD');
} else if (data == "seasonfolders") {
$('#naming_abd_pattern').qtip('option', {
'content.text': 'This pattern would be invalid without the folders, using it will force "Flatten" off for all shows.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_abd_pattern').qtip('toggle', true);
$('#naming_abd_pattern').css('background-color', '#FFFFDD');
} else {
$('#naming_abd_pattern').qtip('option', {
'content.text': 'This pattern is valid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-green'
'style.classes': 'qtip-green qtip-rounded qtip-shadow'
});
$('#naming_abd_pattern').qtip('toggle', false);
$('#naming_abd_pattern').css('background-color', '#FFFFFF');
@ -138,21 +138,21 @@ $(document).ready(function () {
if (data == "invalid") {
$('#naming_sports_pattern').qtip('option', {
'content.text': 'This pattern is invalid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_sports_pattern').qtip('toggle', true);
$('#naming_sports_pattern').css('background-color', '#FFDDDD');
} else if (data == "seasonfolders") {
$('#naming_sports_pattern').qtip('option', {
'content.text': 'This pattern would be invalid without the folders, using it will force "Flatten" off for all shows.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_sports_pattern').qtip('toggle', true);
$('#naming_sports_pattern').css('background-color', '#FFFFDD');
} else {
$('#naming_sports_pattern').qtip('option', {
'content.text': 'This pattern is valid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-green'
'style.classes': 'qtip-green qtip-rounded qtip-shadow'
});
$('#naming_sports_pattern').qtip('toggle', false);
$('#naming_sports_pattern').css('background-color', '#FFFFFF');
@ -179,21 +179,21 @@ $(document).ready(function () {
if (data == "invalid") {
$('#naming_sports_pattern').qtip('option', {
'content.text': 'This pattern is invalid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_sports_pattern').qtip('toggle', true);
$('#naming_sports_pattern').css('background-color', '#FFDDDD');
} else if (data == "seasonfolders") {
$('#naming_sports_pattern').qtip('option', {
'content.text': 'This pattern would be invalid without the folders, using it will force "Flatten" off for all shows.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_sports_pattern').qtip('toggle', true);
$('#naming_sports_pattern').css('background-color', '#FFFFDD');
} else {
$('#naming_sports_pattern').qtip('option', {
'content.text': 'This pattern is valid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-green'
'style.classes': 'qtip-green qtip-rounded qtip-shadow'
});
$('#naming_sports_pattern').qtip('toggle', false);
$('#naming_sports_pattern').css('background-color', '#FFFFFF');
@ -232,21 +232,21 @@ $(document).ready(function () {
if (data == "invalid") {
$('#naming_anime_pattern').qtip('option', {
'content.text': 'This pattern is invalid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_anime_pattern').qtip('toggle', true);
$('#naming_anime_pattern').css('background-color', '#FFDDDD');
} else if (data == "seasonfolders") {
$('#naming_anime_pattern').qtip('option', {
'content.text': 'This pattern would be invalid without the folders, using it will force "Flatten" off for all shows.',
'style.classes': 'qtip-rounded qtip-shadow qtip-red'
'style.classes': 'qtip-red qtip-rounded qtip-shadow'
});
$('#naming_anime_pattern').qtip('toggle', true);
$('#naming_anime_pattern').css('background-color', '#FFFFDD');
} else {
$('#naming_anime_pattern').qtip('option', {
'content.text': 'This pattern is valid.',
'style.classes': 'qtip-rounded qtip-shadow qtip-green'
'style.classes': 'qtip-green qtip-rounded qtip-shadow'
});
$('#naming_anime_pattern').qtip('toggle', false);
$('#naming_anime_pattern').css('background-color', '#FFFFFF');
@ -485,35 +485,27 @@ $(document).ready(function () {
}
$(this).refreshMetadataConfig(true);
$('img[title]').qtip( {
$('img[title]').qtip({
position: {
viewport: $(window),
at: 'bottom center',
my: 'top right'
my: 'top right',
at: 'bottom center'
},
style: {
tip: {
corner: true,
method: 'polygon'
},
classes: 'qtip-shadow qtip-dark'
classes: 'qtip-dark qtip-rounded qtip-shadow'
}
});
$('i[title]').qtip( {
$('i[title]').qtip({
position: {
viewport: $(window),
at: 'top center',
my: 'bottom center'
my: 'bottom center',
at: 'top center'
},
style: {
tip: {
corner: true,
method: 'polygon'
},
classes: 'qtip-rounded qtip-shadow ui-tooltip-sb'
classes: 'qtip-rounded qtip-shadow'
}
});
$('.custom-pattern,#unpack').qtip( {
$('.custom-pattern,#unpack').qtip({
content: 'validating...',
show: {
event: false,
@ -522,15 +514,11 @@ $(document).ready(function () {
hide: false,
position: {
viewport: $(window),
at: 'center left',
my: 'center right'
my: 'right center',
at: 'left center'
},
style: {
tip: {
corner: true,
method: 'polygon'
},
classes: 'qtip-rounded qtip-shadow qtip-red'
classes: 'qtip-red qtip-rounded qtip-shadow'
}
});

View file

@ -13,14 +13,14 @@ $(document).ready(function () {
$(this).val('jump');
});
$("#prevShow").click(function () {
$('#pickShow option:selected').prev('option').attr('selected', 'selected');
$("#pickShow").change();
});
$("#nextShow").click(function () {
$('#pickShow option:selected').next('option').attr('selected', 'selected');
$("#pickShow").change();
$('#prevShow, #nextShow').click(function () {
var select$ = $('#pickShow'),
index = $.inArray(select$.find('option:selected').val()*1, TVShowList);
select$.find('option[value="' + TVShowList[('nextShow' === $(this).attr('id')
? (index < TVShowList.length - 1 ? index + 1 : 0)
: (0 < index ? index - 1 : TVShowList.length - 1))] + '"]').attr('selected', 'selected');
select$.change();
return false;
});
$('#changeStatus').click(function () {

View file

@ -1,111 +1,136 @@
/*jQuery Form to Form Wizard (Initial: Oct 1st, 2010)
* This notice must stay intact for usage
* Author: Dynamic Drive at http://www.dynamicdrive.com/
* Visit http://www.dynamicdrive.com/ for full source code
*/
/* jQuery Form to Form Wizard (Initial: Oct 1st, 2010)
* This notice must stay intact for usage
* Author: Dynamic Drive at http://www.dynamicdrive.com/
* Visit http://www.dynamicdrive.com/ for full source code
*/
//Oct 21st, 2010: Script updated to v1.1, which adds basic form validation functionality, triggered each time the user goes from one page to the next, or tries to submit the form.
//jQuery.noConflict()
// Oct 21st, 2010: Script updated to v1.1, which adds basic form validation functionality, triggered each time the user goes from one page to the next, or tries to submit the form.
// jQuery.noConflict()
function formtowizard(options){
this.setting=jQuery.extend({persistsection:false, revealfx:['slide', 500], oninit:function(){}, onpagechangestart:function(){}}, options)
this.currentsection=-1
function FormToWizard(options){
this.setting = jQuery.extend({fieldsetborderwidth:2, persistsection:false, revealfx:['slide', 500],
oninit:function(){}, onpagechangestart:function(){}}, options);
this.currentsection = -1;
this.init(this.setting)
}
formtowizard.prototype={
createfieldsets:function($theform, arr){ //reserved function for future version (dynamically wraps form elements with a fieldset element)
$theform.find('fieldset.sectionwrap').removeClass('sectionwrap') //make sure no fieldsets carry 'sectionwrap' before proceeding
var $startelement=$theform.find(':first-child') //reference first element inside form
for (var i=0; i<arr.length; i++){ //loop thru "break" elements
var $fieldsetelements=$startelement.nextUntil('#'+arr[i].breakafter+', *[name='+arr[i].breakafter+']').andSelf() //reference all elements from start element to break element (nextUntil() is jQuery 1.4 function)
$fieldsetelements.add($fieldsetelements.next()).wrapAll('<fieldset class="sectionwrap" />') //wrap these elements with fieldset element
$startelement=$theform.find('fieldset.sectionwrap').eq(i).prepend('<legend class="legendStep">'+arr[i].legend+'</legend>').next() //increment startelement to begin at the end of the just inserted fieldset element
}
},
FormToWizard.prototype = {
loadsection:function(rawi, bypasshooks){
var thiswizard=this
//doload Boolean checks to see whether to load next section (true if bypasshooks param is true or onpagechangestart() event handler doesn't return false)
var doload=bypasshooks || this.setting.onpagechangestart(jQuery, this.currentsection, this.sections.$sections.eq(this.currentsection))
doload=(doload===false)? false : true //unless doload is explicitly false, set to true
if (!bypasshooks && this.setting.validate){
var outcome=this.validate(this.currentsection)
if (outcome===false)
doload=false
}
var i=(rawi=="prev")? this.currentsection-1 : (rawi=="next")? this.currentsection+1 : parseInt(rawi) //get index of next section to show
i=(i<0)? this.sections.count-1 : (i>this.sections.count-1)? 0 : i //make sure i doesn't exceed min/max limit
if (i<this.sections.count && doload){ //if next section to show isn't the same as the current section shown
this.$thesteps.eq(this.currentsection).addClass('disabledstep').end().eq(i).removeClass('disabledstep') //dull current "step" text then highlight next "step" text
if (this.setting.revealfx[0]=="slide"){
this.sections.$sections.css("visibility", "visible")
this.sections.$outerwrapper.stop().animate({height: this.sections.$sections.eq(i).outerHeight()}, this.setting.revealfx[1]) //animate fieldset wrapper's height to accomodate next section's height
this.sections.$innerwrapper.stop().animate({left:-i*this.maxfieldsetwidth}, this.setting.revealfx[1], function(){ //slide next section into view
thiswizard.sections.$sections.each(function(thissec){
if (thissec!=i) //hide fieldset sections currently not in veiw, so tabbing doesn't go to elements within them (and mess up layout)
thiswizard.sections.$sections.eq(thissec).css("visibility", "hidden")
//doload Boolean checks to see whether to load next section (true if bypasshooks param is true or onpagechangestart() event handler doesn't return false)
var doload = bypasshooks || this.setting.onpagechangestart(jQuery, this.currentsection, this.sections.$sections.eq(this.currentsection)),
tabIndex,
thiswizard = this;
doload = (doload !== false); //unless doload is explicitly false, set to true
if (!bypasshooks && this.setting.validate && false === this.validate(this.currentsection))
doload = false;
//get index of next section to show
tabIndex = ('prev' == rawi
? this.currentsection - 1
: ('next' == rawi
? this.currentsection + 1
: parseInt(rawi)));
//don't exceed min/max limit
tabIndex = (tabIndex < 0
? this.sections.count - 1
: (tabIndex > (this.sections.count - 1)
? 0
: tabIndex));
//if next section to show isn't the same as the current section shown
if (tabIndex < this.sections.count && doload){
//dull current 'step' text then highlight next 'step' text
this.$thesteps.eq(this.currentsection).addClass('disabledstep').end().eq(tabIndex).removeClass('disabledstep');
if ('slide' == this.setting.revealfx[0]) {
this.sections.$sections.css('visibility', 'visible');
//animate fieldset wrapper's height to accommodate next section's height
this.sections.$outerwrapper.stop().animate({height: this.sections.$sections.eq(tabIndex).outerHeight()}, this.setting.revealfx[1]);
//slide next section into view
this.sections.$innerwrapper.stop().animate({left: -tabIndex * this.maxfieldsetwidth}, this.setting.revealfx[1], function () {
thiswizard.sections.$sections.each(function (thissec) {
//hide fieldset sections currently not in view, so tabbing doesn't go to elements within them (and mess up layout)
if (tabIndex != thissec)
thiswizard.sections.$sections.eq(thissec).css('visibility', 'hidden')
})
})
}
else if (this.setting.revealfx[0]=="fade"){ //if fx is "fade"
this.sections.$sections.eq(this.currentsection).hide().end().eq(i).fadeIn(this.setting.revealfx[1], function(){
else if ('fade' == this.setting.revealfx[0]) { //if fx is 'fade'
this.sections.$sections.eq(this.currentsection).hide().end().eq(tabIndex).fadeIn(this.setting.revealfx[1], function () {
if (document.all && this.style && this.style.removeAttribute)
this.style.removeAttribute('filter') //fix IE clearType problem
//fix IE clearType problem
this.style.removeAttribute('filter');
})
} else {
this.sections.$sections.eq(this.currentsection).hide().end().eq(tabIndex).show()
}
else{
this.sections.$sections.eq(this.currentsection).hide().end().eq(i).show()
}
this.paginatediv.$status.text("Page "+(i+1)+" of "+this.sections.count) //update current page status text
this.paginatediv.$navlinks.css('visibility', 'visible')
if (i==0) //hide "prev" link
this.paginatediv.$navlinks.eq(0).css('visibility', 'hidden')
else if (i==this.sections.count-1) //hide "next" link
this.paginatediv.$navlinks.eq(1).css('visibility', 'hidden')
//update current page status text
this.paginatediv.$status.text('step ' + (tabIndex + 1) + ' / ' + this.sections.count);
this.paginatediv.$navlinks.css('visibility', 'visible');
if (0 == tabIndex) //hide 'prev' link
this.paginatediv.$navlinks.eq(0).css('visibility', 'hidden');
else if ((this.sections.count - 1) == tabIndex)
//hide 'next' link
this.paginatediv.$navlinks.eq(1).css('visibility', 'hidden');
if (this.setting.persistsection) //enable persistence?
formtowizard.routines.setCookie(this.setting.formid+"_persist", i)
this.currentsection=i
if(i === 0) { setTimeout(function() { $('#nameToSearch').focus(); }, 250); }
FormToWizard.routines.setCookie(this.setting.formid + '_persist', tabIndex);
this.currentsection = tabIndex;
if (0 === tabIndex) {
setTimeout(function () {
$('#nameToSearch').focus();
}, 250);
}
}
},
addvalidatefields:function(){
var $=jQuery, setting=this.setting, theform=this.$theform.get(0), validatefields=[]
var validatefields=setting.validate //array of form element ids to validate
for (var i=0; i<validatefields.length; i++){
var el=theform.elements[validatefields[i]] //reference form element
if (el){ //if element is defined
var $section=$(el).parents('fieldset.sectionwrap:eq(0)') //find fieldset.sectionwrap this form element belongs to
if ($section.length==1){ //if element is within a fieldset.sectionwrap element
$section.data('elements').push(el) //cache this element inside corresponding section
var $ = jQuery,
setting = this.setting,
theform = this.$theform.get(0),
validatefields = setting.validate; //array of form element ids to validate
for (var i = 0; i < validatefields.length; i++){
var el = theform.elements[validatefields[i]]; //reference form element
if (el){
//find fieldset.sectionwrap this form element belongs to
var $section = $(el).parents('fieldset.sectionwrap:eq(0)');
//if element is within a fieldset.sectionwrap element
if ($section.length == 1){
//cache this element inside corresponding section
$section.data('elements').push(el);
}
}
}
},
validate:function(section){
var elements=this.sections.$sections.eq(section).data('elements') //reference elements within this section that should be validated
var validated=true, invalidtext=["Please fill out the following fields:\n"]
//reference elements within this section that should be validated
var elements = this.sections.$sections.eq(section).data('elements');
var validated = true, invalidtext = ['Please fill out the following fields:' + "\n"];
function invalidate(el){
validated=false
invalidtext.push("- "+ (el.id || el.name))
validated = false;
invalidtext.push('- '+ (el.id || el.name))
}
for (var i=0; i<elements.length; i++){
if (/(text)/.test(elements[i].type) && elements[i].value==""){ //text and textarea elements
for (var i = 0; i < elements.length; i++){
if (/(text)/.test(elements[i].type) && elements[i].value == ''){
//text and textarea elements
invalidate(elements[i])
}
else if (/(select)/.test(elements[i].type) && (elements[i].selectedIndex==-1 || elements[i].options[elements[i].selectedIndex].text=="")){ //select elements
} else if (/(select)/.test(elements[i].type) && (elements[i].selectedIndex == -1 || elements[i].options[elements[i].selectedIndex].text == '')){
//select elements
invalidate(elements[i])
}
else if (elements[i].type==undefined && elements[i].length>0){ //radio and checkbox elements
var onechecked=false
for (var r=0; r<elements[i].length; r++){
if (elements[i][r].checked==true){
onechecked=true
} else if (undefined == elements[i].type && 0 < elements[i].length){
//radio and checkbox elements
var onechecked = false;
for (var r = 0; r < elements[i].length; r++){
if (elements[i][r].checked == true){
onechecked = true;
break
}
}
@ -115,98 +140,154 @@ formtowizard.prototype={
}
}
if (!validated)
alert(invalidtext.join('\n'))
alert(invalidtext.join("\n"));
return validated
},
init:function(setting){
var thiswizard=this
var thiswizard = this;
jQuery(function($){ //on document.ready
var $theform=$('#'+setting.formid)
if ($theform.length==0) //if form with specified ID doesn't exist, try name attribute instead
$theform=$('form[name='+setting.formid+']')
if (setting.manualfieldsets && setting.manualfieldsets.length>0)
thiswizard.createfieldsets($theform, setting.manualfieldsets)
var $stepsguide=$('<div class="stepsguide" />') //create Steps Container to house the "steps" text
var $sections=$theform.find('fieldset.sectionwrap').hide() //find all fieldset elements within form and hide them initially
if (setting.revealfx[0]=="slide"){ //create outer DIV that will house all the fieldset.sectionwrap elements
$sectionswrapper=$('<div style="position:relative;overflow:hidden;"></div>').insertBefore($sections.eq(0)) //add DIV above the first fieldset.sectionwrap element
$sectionswrapper_inner=$('<div style="position:absolute;left:0;top:0;"></div>') //create inner DIV of $sectionswrapper that will scroll to reveal a fieldset element
var $theform = $('#' + setting.formid),
//create Steps Container to house the 'steps' text
$stepsguide = $('<div class="stepsguide" />'),
//find all fieldsets within form and hide them initially
$sections = $theform.find('fieldset.sectionwrap').hide(),
$sectionswrapper = '',
$sectionswrapper_inner = '';
if (0 == $theform.length)
//if form with specified ID doesn't exist, try name attribute instead
$theform = $('form[name=' + setting.formid + ']');
if ('slide' == setting.revealfx[0]) {
//create outer DIV that will house all the fieldset.sectionwrap elements
//add DIV above the first fieldset.sectionwrap element
$sectionswrapper = $('<div class="step-outer" style="position:relative"></div>').insertBefore($sections.eq(0));
//create inner DIV of $sectionswrapper that will scroll to reveal a fieldset element
$sectionswrapper_inner = $('<div class="step-inner" style="position:absolute; left:0"></div>');
}
var maxfieldsetwidth=$sections.eq(0).outerWidth() //variable to get width of widest fieldset.sectionwrap
$sections.slice(1).each(function(i){ //loop through $sections (starting from 2nd one)
maxfieldsetwidth=Math.max($(this).outerWidth(), maxfieldsetwidth)
})
maxfieldsetwidth+=2 //add 2px to final width to reveal fieldset border (if not removed via CSS)
thiswizard.maxfieldsetwidth=maxfieldsetwidth
$sections.each(function(i){ //loop through $sections again
var $section=$(this)
if (setting.revealfx[0]=="slide"){
$section.data('page', i).css({position:'absolute', top:0, left:maxfieldsetwidth*i}).appendTo($sectionswrapper_inner) //set fieldset position to "absolute" and move it to inside sectionswrapper_inner DIV
//variable to get width of widest fieldset.sectionwrap
var maxfieldsetwidth = $sections.eq(0).outerWidth();
//loop through $sections (starting from 2nd one)
$sections.slice(1).each(function(){
maxfieldsetwidth = Math.max($(this).outerWidth(), maxfieldsetwidth)
});
//add default 2px or param px to final width to reveal fieldset border (if not removed via CSS)
maxfieldsetwidth += setting.fieldsetborderwidth;
thiswizard.maxfieldsetwidth = maxfieldsetwidth;
//loop through $sections again
$sections.each(function(i){
var $section = $(this);
if ('slide' == setting.revealfx[0]) {
//set fieldset position to 'absolute' and move it to inside sectionswrapper_inner DIV
$section.data('page', i).css({position: 'absolute', left: maxfieldsetwidth * i}).appendTo($sectionswrapper_inner);
}
$section.data('elements', []) //empty array to contain elements within this section that should be validated for data (applicable only if validate option is defined)
//create each "step" DIV and add it to main Steps Container:
var $thestep=$('<div class="step disabledstep" />').data('section', i).html('Step '+(i+1)+'<div class="smalltext">'+$section.find('legend:eq(0)').text()+'<p></p></div>').appendTo($stepsguide)
$thestep.click(function(){ //assign behavior to each step div
//empty array to contain elements within this section that should be validated for data (applicable only if validate option is defined)
$section.data('elements', []);
//create each 'step' DIV and add it to main Steps Container:
var $stepwords = ['first', 'second', 'third'], $thestep = $('<div class="step disabledstep" />').data('section', i).html(($stepwords[i]
+ ' step') + '<div class="smalltext">' + $section.find('legend:eq(0)').text() + '<p></p></div>').appendTo($stepsguide);
//assign behavior to each step div
$thestep.click(function(){
thiswizard.loadsection($(this).data('section'))
})
})
if (setting.revealfx[0]=="slide"){
$sectionswrapper.width(maxfieldsetwidth) //set fieldset wrapper to width of widest fieldset
$sectionswrapper.append($sectionswrapper_inner) //add $sectionswrapper_inner as a child of $sectionswrapper
});
if ('slide' == setting.revealfx[0]) {
$sectionswrapper.width(maxfieldsetwidth); //set fieldset wrapper to width of widest fieldset
$sectionswrapper.append($sectionswrapper_inner); //add $sectionswrapper_inner as a child of $sectionswrapper
$stepsguide.append('<div style="clear:both">&nbsp;</div>')
}
$theform.prepend($stepsguide) //add $thesteps div to the beginning of the form
//add $thesteps div to the beginning of the form
$theform.prepend($stepsguide);
//$stepsguide.insertBefore($sectionswrapper) //add Steps Container before sectionswrapper container
var $thesteps=$stepsguide.find('div.step')
var $thesteps = $stepsguide.find('div.step');
//create pagination DIV and add it to end of form:
var $paginatediv=$('<div class="formpaginate" style="overflow:hidden;"><span class="prev" style="float:left">Prev</span> <span class="status">Step 1 of </span> <span class="next" style="float:right">Next</span></div>')
$theform.append($paginatediv)
thiswizard.$theform=$theform
if (setting.revealfx[0]=="slide"){
thiswizard.sections={$outerwrapper:$sectionswrapper, $innerwrapper:$sectionswrapper_inner, $sections:$sections, count:$sections.length} //remember various parts of section container
var $paginatediv = $('<div class="formpaginate">'
+ '<span class="prev" style="float:left">Prev</span>'
+ ' <span class="status">step 1 of </span>'
+ ' <span class="next" style="float:right">Next</span>'
+ '</div>');
$theform.append($paginatediv);
thiswizard.$theform = $theform;
if ('slide' == setting.revealfx[0]) {
//remember various parts of section container
thiswizard.sections = {
$outerwrapper: $sectionswrapper,
$innerwrapper: $sectionswrapper_inner,
$sections: $sections,
count: $sections.length
};
thiswizard.sections.$sections.show()
} else {
//remember various parts of section container
thiswizard.sections = {
$sections: $sections,
count: $sections.length
};
}
else{
thiswizard.sections={$sections:$sections, count:$sections.length} //remember various parts of section container
}
thiswizard.$thesteps=$thesteps //remember this ref
thiswizard.paginatediv={$main:$paginatediv, $navlinks:$paginatediv.find('span.prev, span.next'), $status:$paginatediv.find('span.status')} //remember various parts of pagination DIV
thiswizard.paginatediv.$main.click(function(e){ //assign behavior to pagination buttons
thiswizard.$thesteps = $thesteps;
//remember various parts of pagination DIV
thiswizard.paginatediv = {
$main: $paginatediv,
$navlinks: $paginatediv.find('span.prev, span.next'),
$status: $paginatediv.find('span.status')
};
//assign behavior to pagination buttons
thiswizard.paginatediv.$main.click(function(e){
if (/(prev)|(next)/.test(e.target.className))
thiswizard.loadsection(e.target.className)
})
var i=(setting.persistsection)? formtowizard.routines.getCookie(setting.formid+"_persist") : 0
thiswizard.loadsection(i||0, true) //show the first section
thiswizard.setting.oninit($, i, $sections.eq(i)) //call oninit event handler
if (setting.validate){ //if validate array defined
thiswizard.addvalidatefields() //seek out and cache form elements that should be validated
});
var i = (setting.persistsection ? FormToWizard.routines.getCookie(setting.formid + '_persist') : 0);
//show the first section
thiswizard.loadsection(i||0, true);
//call oninit event handler
thiswizard.setting.oninit($, i, $sections.eq(i));
//if validate array defined
if (setting.validate){
//seek out and cache form elements that should be validated
thiswizard.addvalidatefields();
thiswizard.$theform.submit(function(){
var returnval=true
for (var i=0; i<thiswizard.sections.count; i++){
for (var i = 0; i < thiswizard.sections.count; i++){
if (!thiswizard.validate(i)){
thiswizard.loadsection(i, true)
returnval=false
break
thiswizard.loadsection(i, true);
return false;
}
}
return returnval //allow or disallow form submission
return true;
})
}
})
}
}
};
formtowizard.routines={
FormToWizard.routines = {
getCookie:function(Name){
var re=new RegExp(Name+"=[^;]+", "i"); //construct RE to search for target name/value pair
getCookie:function(Name){
var re = new RegExp(Name + '=[^;]+', 'i'); //construct RE to search for target name/value pair
if (document.cookie.match(re)) //if cookie found
return document.cookie.match(re)[0].split("=")[1] //return its value
return document.cookie.match(re)[0].split('=')[1]; //return its value
return null
},
setCookie:function(name, value){
document.cookie = name+"=" + value + ";path=/"
document.cookie = name + '=' + value + ';path=/';
}
}
};

View file

@ -54,11 +54,7 @@
}
},
style: {
tip: {
corner: true,
method: 'polygon'
},
classes: 'qtip-rounded qtip-dark qtip-shadow ui-tooltip-sb'
classes: 'qtip-dark qtip-rounded qtip-shadow'
}
});
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,34 +1,40 @@
$(document).ready(function() {
function make_row(indexer_id, season, episode, name, checked) {
if (checked)
var checked = ' checked';
else
var checked = '';
var row_class = $('#row_class').val();
var row = '';
row += ' <tr class="'+row_class+'">';
row += ' <td class="tableleft" align="center"><input type="checkbox" class="'+indexer_id+'-epcheck" name="'+indexer_id+'-'+season+'x'+episode+'"'+checked+'></td>';
row += ' <td>'+season+'x'+episode+'</td>';
row += ' <td class="tableright" style="width: 100%">'+name+'</td>';
row += ' </tr>'
return row;
var checkedbox = (checked ? ' checked' : ''),
row_class = $('#row_class').val();
return ' <tr class="' + row_class + '">'
+ ' <td class="tableleft" align="center">'
+ '<input type="checkbox"'
+ ' class="' + indexer_id + '-epcheck"'
+ ' name="' + indexer_id + '-' + season + 'x' + episode + '"'
+ checkedbox+'></td>'
+ ' <td>' + season + 'x' + episode + '</td>'
+ ' <td class="tableright" style="width: 100%">' + name + '</td>'
+ ' </tr>';
}
$('.go').click(function() {
var selected;
if (selected = (0 === $('input[class*="-epcheck"]:checked').length))
alert('Please select at least one episode');
return !selected
});
$('.allCheck').click(function(){
var indexer_id = $(this).attr('id').split('-')[1];
$('.'+indexer_id+'-epcheck').prop('checked', $(this).prop('checked'));
$('.' + indexer_id + '-epcheck').prop('checked', $(this).prop('checked'));
});
$('.get_more_eps').click(function(){
var cur_indexer_id = $(this).attr('id');
var checked = $('#allCheck-'+cur_indexer_id).prop('checked');
var last_row = $('tr#'+cur_indexer_id);
var checked = $('#allCheck-' + cur_indexer_id).prop('checked');
var last_row = $('tr#' + cur_indexer_id);
$.getJSON(sbRoot+'/manage/showEpisodeStatuses',
$.getJSON(sbRoot + '/manage/showEpisodeStatuses',
{
indexer_id: cur_indexer_id,
whichStatus: $('#oldStatus').val()

View file

@ -1,227 +1,278 @@
$(document).ready(function () {
function populateSelect() {
if (!$('#nameToSearch').length) {
return;
}
function populateSelect() {
if (!$('#nameToSearch').length)
return;
if ($('#indexerLangSelect option').length <= 1) {
$.getJSON(sbRoot + '/home/addShows/getIndexerLanguages', {}, function (data) {
var selected, resultStr = '';
if (1 >= $('#indexerLangSelect').find('option').length) {
if (data.results.length === 0) {
resultStr = '<option value="en" selected="selected">en</option>';
} else {
$.each(data.results, function (index, obj) {
if (resultStr == '') {
selected = ' selected="selected"';
} else {
selected = '';
}
$.getJSON(sbRoot + '/home/addShows/getIndexerLanguages', {}, function (data) {
resultStr += '<option value="' + obj + '"' + selected + '>' + obj + '</option>';
});
}
var resultStr = '',
selected = ' selected="selected"',
elIndexerLang = $('#indexerLangSelect');
$('#indexerLangSelect').html(resultStr);
$('#indexerLangSelect').change(function () { searchIndexers(); });
});
}
}
if (0 === data.results.length) {
resultStr = '<option value="en"' + selected + '>en</option>';
} else {
$.each(data.results, function (index, obj) {
resultStr += '<option value="' + obj + '"'
+ ('' == resultStr ? selected : '')
+ '>' + obj + '</option>';
});
}
var searchRequestXhr = null;
elIndexerLang.html(resultStr);
elIndexerLang.change(function () {
searchIndexers();
});
});
}
}
function searchIndexers() {
if (!$('#nameToSearch').val().length) {
return;
}
var searchRequestXhr = null;
if (searchRequestXhr) searchRequestXhr.abort();
function searchIndexers() {
var elNameToSearch = $('#nameToSearch');
var searchingFor = $('#nameToSearch').val() + ' on ' + $('#providedIndexer option:selected').text() + ' in ' + $('#indexerLangSelect').val();
$('#searchResults').empty().html('<img id="searchingAnim" src="' + sbRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> searching ' + searchingFor + '...');
if (!elNameToSearch.val().length)
return;
searchRequestXhr = $.ajax({
url: sbRoot + '/home/addShows/searchIndexersForShowName',
data: {'search_term': $('#nameToSearch').val(), 'lang': $('#indexerLangSelect').val(), 'indexer': $('#providedIndexer').val()},
timeout: parseInt($('#indexer_timeout').val(), 10) * 1000,
dataType: 'json',
error: function () {
$('#searchResults').empty().html('search timed out, try again or try another indexer');
},
success: function (data) {
var firstResult = true;
var resultStr = '<fieldset>\n<legend>Search Results:</legend>\n';
var checked = '';
if (searchRequestXhr)
searchRequestXhr.abort();
if (data.results.length === 0) {
resultStr += '<b>No results found, try a different search.</b>';
} else {
$.each(data.results, function (index, obj) {
if (firstResult) {
checked = ' checked';
firstResult = false;
} else {
checked = '';
}
var elTvDatabase = $('#providedIndexer'),
elIndexerLang = $('#indexerLangSelect'),
searchingFor = elNameToSearch.val() + ' on ' + elTvDatabase.find('option:selected').text() + ' in ' + elIndexerLang.val();
var whichSeries = obj.join('|');
$('#searchResults').empty().html('<img id="searchingAnim" src="' + sbRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> searching ' + searchingFor + '...');
searchRequestXhr = $.ajax({
url: sbRoot + '/home/addShows/searchIndexersForShowName',
data: {
'search_term': elNameToSearch.val(),
'lang': elIndexerLang.val(),
'indexer': elTvDatabase.val()
},
timeout: parseInt($('#indexer_timeout').val(), 10) * 1000,
dataType: 'json',
error: function () {
$('#searchResults').empty().html('search timed out, try again or try another database');
},
success: function (data) {
var resultStr = '', checked = '', rowType, row = 0;
resultStr += '<input type="radio" id="whichSeries" name="whichSeries" value="' + whichSeries + '"' + checked + ' /> ';
if (data.langid && data.langid != "") {
resultStr += '<a href="' + anonURL + obj[2] + obj[3] + '&lid=' + data.langid + '" onclick=\"window.open(this.href, \'_blank\'); return false;\" ><b>' + obj[4] + '</b></a>';
} else {
resultStr += '<a href="' + anonURL + obj[2] + obj[3] + '" onclick=\"window.open(this.href, \'_blank\'); return false;\" ><b>' + obj[4] + '</b></a>';
}
if (0 === data.results.length) {
resultStr += '<span class="boldest">Sorry, no results found. Try a different search.</span>';
} else {
$.each(data.results, function (index, obj) {
checked = (0 == row ? ' checked' : '');
rowType = (0 == row % 2 ? '' : ' class="alt"');
row++;
if (obj[5] !== null) {
var startDate = new Date(obj[5]);
var today = new Date();
if (startDate > today) {
resultStr += ' (will debut on ' + obj[5] + ')';
} else {
resultStr += ' (started on ' + obj[5] + ')';
}
}
var whichSeries = obj.join('|'),
showstartdate = '';
if (obj[0] !== null) {
resultStr += ' [' + obj[0] + ']';
}
if (null !== obj[5]) {
var startDate = new Date(obj[5]);
var today = new Date();
showstartdate = '&nbsp;<span class="stepone-result-date">('
+ (startDate > today ? 'will debut' : 'started')
+ ' on ' + obj[5] + ')</span>';
}
resultStr += '<br />';
});
resultStr += '</ul>';
}
resultStr += '</fieldset>';
$('#searchResults').html(resultStr);
updateSampleText();
myform.loadsection(0);
}
});
}
resultStr += '<div' + rowType + '>'
+ '<input id="whichSeries" type="radio"'
+ ' class="stepone-result-radio"'
+ ' title="Add show <span style=\'color: rgb(66, 139, 202)\'>' + obj[4] + '</span>"'
+ ' name="whichSeries"'
+ ' value="' + whichSeries + '"'
+ checked
+ ' />'
+ '<a'
+ ' class="stepone-result-title"'
+ ' title="View detail for <span style=\'color: rgb(66, 139, 202)\'>' + obj[4] + '</span>"'
+ ' href="' + anonURL + obj[2] + obj[3] + ((data.langid && '' != data.langid) ? '&lid=' + data.langid : '') + '"'
+ ' onclick="window.open(this.href, \'_blank\'); return false;"'
+ '>' + obj[4] + '</a>'
+ showstartdate
+ (null == obj[0] ? ''
: '&nbsp;<span class="stepone-result-db grey-text">' + '[' + obj[0] + ']' + '</span>')
+ '</div>' + "\n";
});
}
$('#searchResults').html(
'<fieldset>' + "\n" + '<legend class="legendStep" style="margin-bottom: 15px">'
+ (0 < row ? row : 'No')
+ ' search result' + (1 == row ? '' : 's') + '...</legend>' + "\n"
+ resultStr
+ '</fieldset>'
);
updateSampleText();
myform.loadsection(0);
$('.stepone-result-radio, .stepone-result-title').each(addQTip);
}
});
}
$('#searchName').click(function () { searchIndexers(); });
var elNameToSearch = $('#nameToSearch'),
elSearchName = $('#searchName');
if ($('#nameToSearch').length && $('#nameToSearch').val().length) {
$('#searchName').click();
}
elSearchName.click(function () { searchIndexers(); });
$('#addShowButton').click(function () {
// if they haven't picked a show don't let them submit
if (!$("input:radio[name='whichSeries']:checked").val() && !$("input:hidden[name='whichSeries']").val().length) {
alert('You must choose a show to continue');
return false;
}
if (elNameToSearch.length && elNameToSearch.val().length) {
elSearchName.click();
}
$('#addShowForm').submit();
});
$('#addShowButton').click(function () {
// if they haven't picked a show don't let them submit
if (!$('input:radio[name="whichSeries"]:checked').val()
&& !$('input:hidden[name="whichSeries"]').val().length) {
alert('You must choose a show to continue');
return false;
}
$('#addShowForm').submit();
});
$('#skipShowButton').click(function () {
$('#skipShow').val('1');
$('#addShowForm').submit();
});
$('#skipShowButton').click(function () {
$('#skipShow').val('1');
$('#addShowForm').submit();
});
$('#qualityPreset').change(function () {
myform.loadsection(2);
});
$('#qualityPreset').change(function () {
myform.loadsection(2);
});
/***********************************************
* jQuery Form to Form Wizard- (c) Dynamic Drive (www.dynamicdrive.com)
* This notice MUST stay intact for legal use
* Visit http://www.dynamicdrive.com/ for this script and 100s more.
***********************************************/
/***********************************************
* jQuery Form to Form Wizard- (c) Dynamic Drive (www.dynamicdrive.com)
* This notice MUST stay intact for legal use
* Visit http://www.dynamicdrive.com/ for this script and 100s more.
***********************************************/
var myform = new formtowizard({
formid: 'addShowForm',
revealfx: ['slide', 500],
oninit: function () {
populateSelect();
updateSampleText();
if ($('input:hidden[name=whichSeries]').length && $('#fullShowPath').length) {
goToStep(3);
}
}
});
var myform = new FormToWizard({
fieldsetborderwidth: 0,
formid: 'addShowForm',
revealfx: ['slide', 500],
oninit: function () {
populateSelect();
updateSampleText();
if ($('input:hidden[name="whichSeries"]').length && $('#fullShowPath').length) {
goToStep(3);
}
}
});
function goToStep(num) {
$('.step').each(function () {
if ($.data(this, 'section') + 1 == num) {
$(this).click();
}
});
}
function goToStep(num) {
$('.step').each(function () {
if ($.data(this, 'section') + 1 == num) {
$(this).click();
}
});
}
$('#nameToSearch').focus();
elNameToSearch.focus();
function updateSampleText() {
// if something's selected then we have some behavior to figure out
function updateSampleText() {
// if something's selected then we have some behavior to figure out
var show_name, sep_char;
// if they've picked a radio button then use that
if ($('input:radio[name=whichSeries]:checked').length) {
show_name = $('input:radio[name=whichSeries]:checked').val().split('|')[4];
}
// if we provided a show in the hidden field, use that
else if ($('input:hidden[name=whichSeries]').length && $('input:hidden[name=whichSeries]').val().length) {
show_name = $('#providedName').val();
} else {
show_name = '';
}
var show_name,
sep_char,
elRadio = $('input:radio[name="whichSeries"]:checked'),
elInput = $('input:hidden[name="whichSeries"]'),
elRootDirs = $('#rootDirs'),
elFullShowPath = $('#fullShowPath');
var sample_text = 'Adding show <b>' + show_name + '</b> into <b>';
// if they've picked a radio button then use that
if (elRadio.length) {
show_name = elRadio.val().split('|')[4];
}
// if we provided a show in the hidden field, use that
else if (elInput.length && elInput.val().length) {
show_name = $('#providedName').val();
} else {
show_name = '';
}
// if we have a root dir selected, figure out the path
if ($("#rootDirs option:selected").length) {
var root_dir_text = $('#rootDirs option:selected').val();
if (root_dir_text.indexOf('/') >= 0) {
sep_char = '/';
} else if (root_dir_text.indexOf('\\') >= 0) {
sep_char = '\\';
} else {
sep_char = '';
}
var sample_text = '<p>Adding show <span class="show-name">' + show_name + '</span>'
+ ('' == show_name ? 'into<br />' : '<br />into')
+ ' <span class="show-dest">';
if (root_dir_text.substr(sample_text.length - 1) != sep_char) {
root_dir_text += sep_char;
}
root_dir_text += '<i>||</i>' + sep_char;
// if we have a root dir selected, figure out the path
if (elRootDirs.find('option:selected').length) {
var root_dir_text = elRootDirs.find('option:selected').val();
if (root_dir_text.indexOf('/') >= 0) {
sep_char = '/';
} else if (root_dir_text.indexOf('\\') >= 0) {
sep_char = '\\';
} else {
sep_char = '';
}
sample_text += root_dir_text;
} else if ($('#fullShowPath').length && $('#fullShowPath').val().length) {
sample_text += $('#fullShowPath').val();
} else {
sample_text += 'unknown dir.';
}
if (root_dir_text.substr(sample_text.length - 1) != sep_char) {
root_dir_text += sep_char;
}
root_dir_text += '<i>||</i>' + sep_char;
sample_text += '</b>';
sample_text += root_dir_text;
} else if (elFullShowPath.length && elFullShowPath.val().length) {
sample_text += elFullShowPath.val();
} else {
sample_text += 'unknown dir.';
}
// if we have a show name then sanitize and use it for the dir name
if (show_name.length) {
$.get(sbRoot + '/home/addShows/sanitizeFileName', {name: show_name}, function (data) {
$('#displayText').html(sample_text.replace('||', data));
});
// if not then it's unknown
} else {
$('#displayText').html(sample_text.replace('||', '??'));
}
sample_text += '</span></p>';
// also toggle the add show button
if (($("#rootDirs option:selected").length || ($('#fullShowPath').length && $('#fullShowPath').val().length)) &&
($('input:radio[name=whichSeries]:checked').length) || ($('input:hidden[name=whichSeries]').length && $('input:hidden[name=whichSeries]').val().length)) {
$('#addShowButton').attr('disabled', false);
} else {
$('#addShowButton').attr('disabled', true);
}
}
// if we have a show name then sanitize and use it for the dir name
if (show_name.length) {
$.get(sbRoot + '/home/addShows/sanitizeFileName', {name: show_name}, function (data) {
$('#displayText').html(sample_text.replace('||', data));
});
// if not then it's unknown
} else {
$('#displayText').html(sample_text.replace('||', '??'));
}
$('#rootDirText').change(updateSampleText);
$('#whichSeries').live('change', updateSampleText);
// also toggle the add show button
if ((elRootDirs.find('option:selected').length || (elFullShowPath.length && elFullShowPath.val().length)) &&
(elRadio.length) || (elInput.length && elInput.val().length)) {
$('#addShowButton').attr('disabled', false);
} else {
$('#addShowButton').attr('disabled', true);
}
}
$('#nameToSearch').keyup(function (event) {
if (event.keyCode == 13) {
$('#searchName').click();
}
});
$('#rootDirText').change(updateSampleText);
$('#searchResults').on('click', '.stepone-result-radio', updateSampleText);
elNameToSearch.keyup(function (event) {
if (event.keyCode == 13) {
elSearchName.click();
}
});
var addQTip = (function() {
$(this).css('cursor', 'help');
$(this).qtip({
show: {
solo: true
},
position: {
viewport: $(window),
my: 'left center',
adjust: {
y: -10,
x: 2
}
},
style: {
tip: {
corner: true,
method: 'polygon'
},
classes: 'qtip-rounded qtip-bootstrap qtip-shadow ui-tooltip-sb'
}
});
});
});

View file

@ -1,20 +1,27 @@
$(function () {
$('.plotInfo').each(function () {
match = $(this).attr("id").match(/^plot_info_(\d+)_(\d+)_(\d+)$/);
var match = $(this).attr('id').match(/^plot_info_(\d+)_(\d+)_(\d+)$/);
$(this).qtip({
content: {
text: 'Loading...',
ajax: {
url: $("#sbRoot").val() + '/home/plotDetails',
type: 'GET',
data: {
show: match[1],
episode: match[3],
season: match[2]
},
success: function (data, status) {
this.set('content.text', data);
}
text: function(event, api) {
// deferred object ensuring the request is only made once
$.ajax({
url: $('#sbRoot').val() + '/home/plotDetails',
type: 'GET',
data: {
show: match[1],
episode: match[3],
season: match[2]
}
})
.then(function(content) {
// Set the tooltip content upon successful retrieval
api.set('content.text', content);
}, function(xhr, status, error) {
// Upon failure... set the tooltip content to the status and error value
api.set('content.text', status + ': ' + error);
});
return 'Loading...'; // Set initial text
}
},
show: {
@ -29,11 +36,7 @@ $(function () {
}
},
style: {
tip: {
corner: true,
method: 'polygon'
},
classes: 'qtip-rounded qtip-shadow ui-tooltip-sb'
classes: 'qtip-rounded qtip-shadow'
}
});
});

View file

@ -1,36 +1,31 @@
function setFromPresets (preset) {
var elCustomQuality = $('#customQuality'),
selected = 'selected';
if (0 == preset) {
elCustomQuality.show();
return;
}
elCustomQuality.hide();
$('#anyQualities').find('option').each(function() {
var result = preset & $(this).val();
$(this).attr(selected, (0 < result ? selected : false));
});
$('#bestQualities').find('option').each(function() {
var result = preset & ($(this).val() << 16);
$(this).attr(selected, (result > 0 ? selected: false));
});
}
$(document).ready(function() {
function setFromPresets (preset) {
if (preset == 0) {
$('#customQuality').show();
return;
} else {
$('#customQuality').hide();
}
var elQualityPreset = $('#qualityPreset'),
selected = ':selected';
$('#anyQualities option').each(function(i) {
var result = preset & $(this).val();
if (result > 0) {
$(this).attr('selected', 'selected');
} else {
$(this).attr('selected', false);
}
});
elQualityPreset.change(function() {
setFromPresets($('#qualityPreset').find(selected).val());
});
$('#bestQualities option').each(function(i) {
var result = preset & ($(this).val() << 16);
if (result > 0) {
$(this).attr('selected', 'selected');
} else {
$(this).attr('selected', false);
}
});
return;
}
$('#qualityPreset').change(function() {
setFromPresets($('#qualityPreset :selected').val());
});
setFromPresets($('#qualityPreset :selected').val());
});
setFromPresets(elQualityPreset.find(selected).val());
});

View file

@ -15,15 +15,11 @@ $(function () {
at: 'center left',
adjust: {
y: 0,
x: -6
x: -2
}
},
style: {
tip: {
corner: true,
method: 'polygon'
},
classes: 'qtip-rounded qtip-shadow ui-tooltip-sb'
classes: 'qtip-rounded qtip-shadow'
}
});
});

View file

@ -1,144 +1,211 @@
$(document).ready(function () {
function getRecommendedShows() {
$.getJSON(sbRoot + '/home/addShows/getRecommendedShows', {}, function (data) {
var firstResult = true;
var resultStr = '<fieldset>\n<legend>Recommended Shows:</legend>\n';
var checked = '';
$(document).ready(function (){
if (data.results.length === 0) {
resultStr += '<b>No recommended shows found, update your watched shows list on trakt.tv.</b>';
} else {
$.each(data.results, function (index, obj) {
if (firstResult) {
checked = ' checked';
firstResult = false;
} else {
checked = '';
}
function getRecommendedShows(){
var whichSeries = obj.join('|');
$('#searchResults').empty().html('<img id="searchingAnim"'
+ ' src="' + sbRoot + '/images/loading32' + themeSpinner + '.gif"'
+ ' height="32" width="32" />'
+ ' fetching recommendations...');
resultStr += '<input type="radio" id="whichSeries" name="whichSeries" value="' + whichSeries + '"' + checked + ' /> ';
resultStr += '<a href="' + anonURL + obj[1] + '" onclick="window.open(this.href, \'_blank\'); return false;"><b>' + obj[2] + '</b></a>';
$.getJSON(sbRoot + '/home/addShows/getRecommendedShows',
{},
function (data){
var resultStr = '', checked = '', rowType, row = 0;
if (obj[4] !== null) {
var startDate = new Date(obj[4]);
var today = new Date();
if (startDate > today) {
resultStr += ' (will debut on ' + obj[4] + ')';
} else {
resultStr += ' (started on ' + obj[4] + ')';
}
}
if (null === data || 0 === data.results.length){
resultStr += '<p>Sorry, no recommended shows found, this can happen from time to time.</p>'
+ '<p>However, if the issue persists, then try updating your watched shows list on trakt.tv</p>';
} else {
if (obj[0] !== null) {
resultStr += ' [' + obj[0] + ']';
}
$.each(data.results, function (index, obj){
checked = (0 == row ? ' checked' : '');
rowType = (0 == row % 2 ? '' : ' class="alt"');
row++;
if (obj[3] !== null) {
resultStr += '<br />' + obj[3];
}
var whichSeries = obj[6] + '|' + obj[0] + '|' + obj[1] + '|' + obj[2] + '|' + obj[3],
showstartdate = '';
resultStr += '<p /><br />';
});
resultStr += '</ul>';
}
resultStr += '</fieldset>';
$('#searchResults').html(resultStr);
updateSampleText();
myform.loadsection(0);
});
}
if (null !== obj[3]){
var startDate = new Date(obj[3]);
var today = new Date();
showstartdate = '&nbsp;<span class="stepone-result-date">('
+ (startDate > today ? 'will debut' : 'started')
+ ' on ' + obj[3] + ')</span>';
}
$('#addShowButton').click(function () {
// if they haven't picked a show don't let them submit
if (!$("input:radio[name='whichSeries']:checked").val() && !$("input:hidden[name='whichSeries']").val().length) {
alert('You must choose a show to continue');
return false;
}
resultStr += '<div' + rowType + '>'
+ '<input id="whichSeries" type="radio"'
+ ' class="stepone-result-radio"'
+ ' style="float:left;margin-top:4px"'
+ ' title="Add show <span style=\'color: rgb(66, 139, 202)\'>' + obj[1] + '</span>"'
+ ' name="whichSeries"'
+ ' value="' + whichSeries + '"'
+ checked
+ ' />'
+ '<div style="margin-left:20px">'
+ '<a'
+ ' class="stepone-result-title"'
+ ' style="margin-left:5px"'
+ ' title="View <span class=\'boldest\'>Trakt</span> detail for <span style=\'color: rgb(66, 139, 202)\'>' + obj[1] + '</span>"'
+ ' href="' + anonURL + obj[0] + '"'
+ ' onclick="window.open(this.href, \'_blank\'); return false;"'
+ '>' + obj[1] + '</a>'
+ showstartdate
+ (null == obj[6] ? ''
: '&nbsp;'
+ '<span class="stepone-result-db grey-text">'
+ '<a class="service" href="' + anonURL + obj[7] + '"'
+ ' onclick="window.open(this.href, \'_blank\'); return false;"'
+ ' title="View <span class=\'boldest\'>' + obj[4] + '</span> detail for <span style=\'color: rgb(66, 139, 202)\'>' + obj[1] + '</span>"'
+ '>'
+ '<img alt="' + obj[4] + '" height="16" width="16" src="' + sbRoot + '/images/' + obj[5] + '" />'
+ ''
+ '</a>'
+ '</span>'
)
+ (null == obj[10] ? ''
: '&nbsp;'
+ '<span class="stepone-result-db grey-text">'
+ '<a class="service" href="' + anonURL + obj[11] + '"'
+ ' onclick="window.open(this.href, \'_blank\'); return false;"'
+ ' title="View <span class=\'boldest\'>' + obj[8] + '</span> detail for <span style=\'color: rgb(66, 139, 202)\'>' + obj[1] + '</span>"'
+ '>'
+ '<img alt="' + obj[8] + '" height="16" width="16" src="' + sbRoot + '/images/' + obj[9] + '" />'
+ ''
+ '</a>'
+ '</span>'
)
+ (null == obj[2] ? ''
: '&nbsp;<div class="stepone-result-overview grey-text">' + obj[2] + '</div>')
+ '</div></div>';
});
}
$('#recommendedShowsForm').submit();
});
$('#searchResults').html(
'<fieldset>' + "\n" + '<legend class="legendStep" style="margin-bottom: 15px">'
+ (0 < row ? row : 'No')
+ ' recommended result' + (1 == row ? '' : 's') + '...</legend>' + "\n"
+ resultStr
+ '</fieldset>'
);
updateSampleText();
myform.loadsection(0);
$('.stepone-result-radio, .stepone-result-title, .service').each(addQTip);
}
);
}
$('#qualityPreset').change(function () {
myform.loadsection(2);
});
$('#addShowButton').click(function () {
// if they haven't picked a show don't let them submit
if (!$('input:radio[name="whichSeries"]:checked').val()
&& !$('input:hidden[name="whichSeries"]').val().length) {
alert('You must choose a show to continue');
return false;
}
$('#addShowForm').submit();
});
var myform = new formtowizard({
formid: 'recommendedShowsForm',
revealfx: ['slide', 500],
oninit: function () {
getRecommendedShows();
updateSampleText();
}
});
$('#qualityPreset').change(function (){
myform.loadsection(2);
});
function goToStep(num) {
$('.step').each(function () {
if ($.data(this, 'section') + 1 == num) {
$(this).click();
}
});
}
var myform = new FormToWizard({
fieldsetborderwidth: 0,
formid: 'addShowForm',
revealfx: ['slide', 500],
oninit: function (){
getRecommendedShows();
updateSampleText();
}
});
function updateSampleText() {
// if something's selected then we have some behavior to figure out
function goToStep(num){
$('.step').each(function (){
if ($.data(this, 'section') + 1 == num){
$(this).click();
}
});
}
var show_name, sep_char;
// if they've picked a radio button then use that
if ($('input:radio[name=whichSeries]:checked').length) {
show_name = $('input:radio[name=whichSeries]:checked').val().split('|')[2];
} else {
show_name = '';
}
function updateSampleText(){
// if something's selected then we have some behavior to figure out
var sample_text = 'Adding show <b>' + show_name + '</b> into <b>';
var elRadio = $('input:radio[name="whichSeries"]:checked'),
elFullShowPath = $('#fullShowPath'),
sep_char = '',
root_dirs = $('#rootDirs'),
// if they've picked a radio button then use that
show_name = (elRadio.length ? elRadio.val().split('|')[2] : ''),
sample_text = '<p>Adding show <span class="show-name">' + show_name + '</span>'
+ ('' == show_name ? 'into<br />' : '<br />into')
+ ' <span class="show-dest">';
// if we have a root dir selected, figure out the path
if ($("#rootDirs option:selected").length) {
var root_dir_text = $('#rootDirs option:selected').val();
if (root_dir_text.indexOf('/') >= 0) {
sep_char = '/';
} else if (root_dir_text.indexOf('\\') >= 0) {
sep_char = '\\';
} else {
sep_char = '';
}
// if we have a root dir selected, figure out the path
if (root_dirs.find('option:selected').length){
var root_dir_text = root_dirs.find('option:selected').val();
if (0 <= root_dir_text.indexOf('/')){
sep_char = '/';
} else if (0 <= root_dir_text.indexOf('\\')){
sep_char = '\\';
}
if (root_dir_text.substr(sample_text.length - 1) != sep_char) {
root_dir_text += sep_char;
}
root_dir_text += '<i>||</i>' + sep_char;
root_dir_text += (sep_char != root_dir_text.substr(sample_text.length - 1)
? sep_char : '')
+ '<i>||</i>' + sep_char;
sample_text += root_dir_text;
} else if ($('#fullShowPath').length && $('#fullShowPath').val().length) {
sample_text += $('#fullShowPath').val();
} else {
sample_text += 'unknown dir.';
}
sample_text += root_dir_text;
} else if (elFullShowPath.length && elFullShowPath.val().length){
sample_text += elFullShowPath.val();
} else {
sample_text += 'unknown dir.';
}
sample_text += '</b>';
sample_text += '</span></p>';
// if we have a show name then sanitize and use it for the dir name
if (show_name.length) {
$.get(sbRoot + '/home/addShows/sanitizeFileName', {name: show_name}, function (data) {
$('#displayText').html(sample_text.replace('||', data));
});
// if not then it's unknown
} else {
$('#displayText').html(sample_text.replace('||', '??'));
}
// if we have a show name then sanitize and use it for the dir name
if (show_name.length){
$.get(sbRoot + '/home/addShows/sanitizeFileName', {name: show_name}, function (data){
$('#displayText').html(sample_text.replace('||', data));
});
// if not then it's unknown
} else {
$('#displayText').html(sample_text.replace('||', '??'));
}
// also toggle the add show button
if (($("#rootDirs option:selected").length || ($('#fullShowPath').length && $('#fullShowPath').val().length)) &&
($('input:radio[name=whichSeries]:checked').length)) {
$('#addShowButton').attr('disabled', false);
} else {
$('#addShowButton').attr('disabled', true);
}
}
// also toggle the add show button
$('#addShowButton').attr('disabled',
((root_dirs.find('option:selected').length
|| (elFullShowPath.length && elFullShowPath.val().length))
&& elRadio.length
? false : true));
}
$('#rootDirText').change(updateSampleText);
$('#whichSeries').live('change', updateSampleText);
var addQTip = (function(){
$(this).css('cursor', 'help');
$(this).qtip({
show: {
solo: true
},
position: {
viewport: $(window),
my: 'left center',
adjust: {
y: -10,
x: 2
}
},
style: {
tip: {
corner: true,
method: 'polygon'
},
classes: 'qtip-rounded qtip-bootstrap qtip-shadow ui-tooltip-sb'
}
});
});
$('#rootDirText').change(updateSampleText);
$('#searchResults').on('click', '.stepone-result-radio', updateSampleText);
});

View file

@ -1,17 +1,25 @@
$(function () {
$('.title span').each(function () {
var match = $(this).parent().attr('id').match(/^scene_exception_(\d+)$/);
$(this).qtip({
content: {
text: 'Loading...',
ajax: {
url: $("#sbRoot").val() + '/home/sceneExceptions',
type: 'GET',
data: {
show: match[1]
},
success: function (data, status) {
this.set('content.text', data);
}
text: function(event, api) {
// deferred object ensuring the request is only made once
$.ajax({
url: $('#sbRoot').val() + '/home/sceneExceptions',
type: 'GET',
data: {
show: match[1]
}
})
.then(function(content) {
// Set the tooltip content upon successful retrieval
api.set('content.text', content);
}, function(xhr, status, error) {
// Upon failure... set the tooltip content to the status and error value
api.set('content.text', status + ': ' + error);
});
return 'Loading...'; // Set initial text
}
},
show: {
@ -19,19 +27,15 @@ $(function () {
},
position: {
viewport: $(window),
my: 'left middle',
at: 'right middle',
my: 'left center',
at: 'right center',
adjust: {
y: 0,
x: 10
x: 2
}
},
style: {
tip: {
corner: true,
method: 'polygon'
},
classes: 'qtip-rounded qtip-shadow ui-tooltip-sb'
classes: 'qtip-rounded qtip-shadow'
}
});
});

282
setup.py
View file

@ -1,282 +0,0 @@
import re
import urllib
import ConfigParser
import sys
import os
import shutil
import zipfile
import subprocess
import fnmatch
import googlecode_upload
from distutils.core import setup
try:
import py2exe
except:
print "The Python module py2exe is required"
sys.exit(1)
try:
import pygithub.github
except:
print "The Python module pyGitHub is required"
sys.exit(1)
# mostly stolen from the SABnzbd package.py file
name = 'SickGear'
version = '0.1'
release = name + '-' + version
Win32ConsoleName = 'SickBeard-console.exe'
Win32WindowName = 'SickBeard.exe'
def findLatestBuild():
regex = "http\://SickGear\.googlecode\.com/files/SickGear\-win32\-alpha\-build(\d+)(?:\.\d+)?\.zip"
svnFile = urllib.urlopen("http://code.google.com/p/SickGear/downloads/list")
for curLine in svnFile.readlines():
match = re.search(regex, curLine)
if match:
groups = match.groups()
return int(groups[0])
return None
def recursive_find_data_files(root_dir, allowed_extensions=('*')):
to_return = {}
for (dirpath, dirnames, filenames) in os.walk(root_dir):
if not filenames:
continue
for cur_filename in filenames:
matches_pattern = False
for cur_pattern in allowed_extensions:
if fnmatch.fnmatch(cur_filename, '*.' + cur_pattern):
matches_pattern = True
if not matches_pattern:
continue
cur_filepath = os.path.join(dirpath, cur_filename)
to_return.setdefault(dirpath, []).append(cur_filepath)
return sorted(to_return.items())
def find_all_libraries(root_dirs):
libs = []
for cur_root_dir in root_dirs:
for (dirpath, dirnames, filenames) in os.walk(cur_root_dir):
if '__init__.py' not in filenames:
continue
libs.append(dirpath.replace(os.sep, '.'))
return libs
def allFiles(dir):
files = []
for file in os.listdir(dir):
fullFile = os.path.join(dir, file)
if os.path.isdir(fullFile):
files += allFiles(fullFile)
else:
files.append(fullFile)
return files
# save the original arguments and replace them with the py2exe args
oldArgs = []
if len(sys.argv) > 1:
oldArgs = sys.argv[1:]
del sys.argv[1:]
sys.argv.append('py2exe')
# clear the dist dir
if os.path.isdir('dist'):
shutil.rmtree('dist')
# root source dir
compile_dir = os.path.dirname(os.path.normpath(os.path.abspath(sys.argv[0])))
if not 'nopull' in oldArgs:
# pull new source from git
print 'Updating source from git'
p = subprocess.Popen('git pull origin master', shell=True, cwd=compile_dir)
o, e = p.communicate()
# figure out what build this is going to be
latestBuild = findLatestBuild()
if 'test' in oldArgs:
currentBuildNumber = str(latestBuild) + 'a'
else:
currentBuildNumber = latestBuild + 1
# set up the compilation options
data_files = recursive_find_data_files('data', ['gif', 'png', 'jpg', 'ico', 'js', 'css', 'tmpl'])
options = dict(
name=name,
version=release,
author='SickGear',
author_email='SickGear@outlook.com',
description=name + ' ' + release,
scripts=['SickBeard.py'],
packages=find_all_libraries(['sickbeard', 'lib']),
)
# set up py2exe to generate the console app
program = [{'script': 'SickBeard.py'}]
options['options'] = {'py2exe':
{
'bundle_files': 3,
'packages': ['Cheetah'],
'excludes': ['Tkconstants', 'Tkinter', 'tcl'],
'optimize': 2,
'compressed': 0
}
}
options['zipfile'] = 'lib/SickGear.zip'
options['console'] = program
options['data_files'] = data_files
# compile sickbeard-console.exe
setup(**options)
# rename the exe to sickbeard-console.exe
try:
if os.path.exists("dist/%s" % Win32ConsoleName):
os.remove("dist/%s" % Win32ConsoleName)
os.rename("dist/%s" % Win32WindowName, "dist/%s" % Win32ConsoleName)
except:
print "Cannot create dist/%s" % Win32ConsoleName
# sys.exit(1)
# we don't need this stuff when we make the 2nd exe
del options['console']
del options['data_files']
options['windows'] = program
# compile sickbeard.exe
setup(**options)
# compile sabToSickbeard.exe using the existing setup.py script
auto_process_dir = os.path.join(compile_dir, 'autoProcessTV')
p = subprocess.Popen([sys.executable, os.path.join(auto_process_dir, 'setup.py')], cwd=auto_process_dir, shell=True)
o, e = p.communicate()
# copy autoProcessTV files to the dist dir
auto_process_files = ['autoProcessTV/sabToSickBeard.py',
'autoProcessTV/hellaToSickBeard.py',
'autoProcessTV/autoProcessTV.py',
'autoProcessTV/autoProcessTV.cfg.sample',
'autoProcessTV/sabToSickBeard.exe']
os.makedirs('dist/autoProcessTV')
for curFile in auto_process_files:
newFile = os.path.join('dist', curFile)
print "Copying file from", curFile, "to", newFile
shutil.copy(curFile, newFile)
# compile updater.exe
setup(
options={'py2exe': {'bundle_files': 1}},
zipfile=None,
console=['updater.py'], requires=['Cheetah']
)
if 'test' in oldArgs:
print "Ignoring changelog for test build"
else:
# start building the CHANGELOG.txt
print 'Creating changelog'
gh = github.GitHub()
# read the old changelog and find the last commit from that build
lastCommit = ""
try:
cl = open("CHANGELOG.txt", "r")
lastCommit = cl.readlines()[0].strip()
cl.close()
except:
print "I guess there's no changelog"
newestCommit = ""
changeString = ""
# cycle through all the git commits and save their commit messages
for curCommit in gh.commits.forBranch('SickGear', 'SickGear'):
if curCommit.id == lastCommit:
break
if newestCommit == "":
newestCommit = curCommit.id
changeString += curCommit.message + "\n\n"
# if we didn't find any changes don't make a changelog file
if newestCommit != "":
newChangelog = open("CHANGELOG.txt", "w")
newChangelog.write(newestCommit + "\n\n")
newChangelog.write("Changelog for build " + str(currentBuildNumber) + "\n\n")
newChangelog.write(changeString)
newChangelog.close()
else:
print "No changes found, keeping old changelog"
# put the changelog in the compile dir
if os.path.exists("CHANGELOG.txt"):
shutil.copy('CHANGELOG.txt', 'dist/')
# figure out what we're going to call the zip file
print 'Zipping files...'
zipFilename = 'SickGear-win32-alpha-build' + str(currentBuildNumber)
if os.path.isfile(zipFilename + '.zip'):
zipNum = 2
while os.path.isfile(zipFilename + '.{0:0>2}.zip'.format(str(zipNum))):
zipNum += 1
zipFilename = zipFilename + '.{0:0>2}'.format(str(zipNum))
# get a list of files to add to the zip
zipFileList = allFiles('dist/')
# add all files to the zip
z = zipfile.ZipFile(zipFilename + '.zip', 'w', zipfile.ZIP_DEFLATED)
for file in zipFileList:
z.write(file, file.replace('dist/', zipFilename + '/'))
z.close()
print "Created zip at", zipFilename
# i store my google code username/pw in a config so i can have this file in public source control
config = ConfigParser.ConfigParser()
configFilename = os.path.join(compile_dir, "gc.ini")
config.read(configFilename)
gc_username = config.get("GC", "username")
gc_password = config.get("GC", "password")
# upload to google code unless I tell it not to
if "noup" not in oldArgs and "test" not in oldArgs:
print "Uploading zip to google code"
googlecode_upload.upload(os.path.abspath(zipFilename + ".zip"), "SickGear", gc_username, gc_password,
"Win32 alpha build " + str(currentBuildNumber) + " (unstable/development release)",
["Featured", "Type-Executable", "OpSys-Windows"])
if 'nopush' not in oldArgs and 'test' not in oldArgs:
# tag commit as a new build and push changes to github
print 'Tagging commit and pushing'
p = subprocess.Popen('git tag -a "build-' + str(currentBuildNumber) + '" -m "Windows build ' + zipFilename + '"',
shell=True, cwd=compile_dir)
o, e = p.communicate()
p = subprocess.Popen('git push --tags origin windows_binaries', shell=True, cwd=compile_dir)
o, e = p.communicate()

View file

@ -147,6 +147,7 @@ ROOT_DIRS = None
UPDATE_SHOWS_ON_START = False
TRASH_REMOVE_SHOW = False
TRASH_ROTATE_LOGS = False
HOME_SEARCH_FOCUS = True
SORT_ARTICLE = False
DEBUG = False
@ -474,7 +475,7 @@ def initialize(consoleLogging=True):
USE_TRAKT, TRAKT_USERNAME, TRAKT_PASSWORD, TRAKT_API, TRAKT_REMOVE_WATCHLIST, TRAKT_USE_WATCHLIST, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, traktCheckerScheduler, TRAKT_USE_RECOMMENDED, TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, \
USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, \
PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, DEFAULT_BACKLOG_FREQUENCY, MIN_BACKLOG_FREQUENCY, BACKLOG_STARTUP, SKIP_REMOVED_FILES, \
showUpdateScheduler, __INITIALIZED__, LAUNCH_BROWSER, UPDATE_SHOWS_ON_START, TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, SORT_ARTICLE, showList, loadingShowList, \
showUpdateScheduler, __INITIALIZED__, LAUNCH_BROWSER, UPDATE_SHOWS_ON_START, TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, HOME_SEARCH_FOCUS, SORT_ARTICLE, showList, loadingShowList, \
NEWZNAB_DATA, NZBS, NZBS_UID, NZBS_HASH, INDEXER_DEFAULT, INDEXER_TIMEOUT, USENET_RETENTION, TORRENT_DIR, \
QUALITY_DEFAULT, FLATTEN_FOLDERS_DEFAULT, SUBTITLES_DEFAULT, STATUS_DEFAULT, DAILYSEARCH_STARTUP, \
GROWL_NOTIFY_ONSNATCH, GROWL_NOTIFY_ONDOWNLOAD, GROWL_NOTIFY_ONSUBTITLEDOWNLOAD, TWITTER_NOTIFY_ONSNATCH, TWITTER_NOTIFY_ONDOWNLOAD, TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD, \
@ -610,6 +611,7 @@ def initialize(consoleLogging=True):
TRASH_REMOVE_SHOW = bool(check_setting_int(CFG, 'General', 'trash_remove_show', 0))
TRASH_ROTATE_LOGS = bool(check_setting_int(CFG, 'General', 'trash_rotate_logs', 0))
HOME_SEARCH_FOCUS = bool(check_setting_int(CFG, 'General', 'home_search_focus', HOME_SEARCH_FOCUS))
SORT_ARTICLE = bool(check_setting_int(CFG, 'General', 'sort_article', 0))
USE_API = bool(check_setting_int(CFG, 'General', 'use_api', 0))
@ -1067,7 +1069,7 @@ def initialize(consoleLogging=True):
# initialize the main SB database
myDB = db.DBConnection()
db.upgradeDatabase(myDB, mainDB.InitialSchema)
db.MigrationCode(myDB)
# initialize the cache database
myDB = db.DBConnection('cache.db')
@ -1430,6 +1432,7 @@ def save_config():
new_config['General']['update_shows_on_start'] = int(UPDATE_SHOWS_ON_START)
new_config['General']['trash_remove_show'] = int(TRASH_REMOVE_SHOW)
new_config['General']['trash_rotate_logs'] = int(TRASH_ROTATE_LOGS)
new_config['General']['home_search_focus'] = int(HOME_SEARCH_FOCUS)
new_config['General']['sort_article'] = int(SORT_ARTICLE)
new_config['General']['proxy_setting'] = PROXY_SETTING
new_config['General']['proxy_indexers'] = int(PROXY_INDEXERS)

View file

@ -98,7 +98,7 @@ class BlackAndWhiteList(object):
def _add_keywords(self, table, range, values):
myDB = db.DBConnection()
for value in values:
myDB.action("INSERT INTO " + table + " (show_id, range , keyword) VALUES (?,?,?)", [self.show_id, range, value])
myDB.action("INSERT INTO [" + table + "] (show_id, range , keyword) VALUES (?,?,?)", [self.show_id, range, value])
self.refresh()
@ -117,18 +117,18 @@ class BlackAndWhiteList(object):
def _del_all_keywords(self, table):
logger.log(u"Deleting all " + table + " keywords for " + str(self.show_id), logger.DEBUG)
myDB = db.DBConnection()
myDB.action("DELETE FROM " + table + " WHERE show_id = ?", [self.show_id])
myDB.action("DELETE FROM [" + table + "] WHERE show_id = ?", [self.show_id])
self.refresh()
def _del_all_keywords_for(self, table, range):
logger.log(u"Deleting all " + range + " " + table + " keywords for " + str(self.show_id), logger.DEBUG)
myDB = db.DBConnection()
myDB.action("DELETE FROM " + table + " WHERE show_id = ? and range = ?", [self.show_id, range])
myDB.action("DELETE FROM [" + table + "] WHERE show_id = ? and range = ?", [self.show_id, range])
self.refresh()
def _load_list(self, table):
myDB = db.DBConnection()
sqlResults = myDB.select("SELECT range,keyword FROM " + table + " WHERE show_id = ? ", [self.show_id])
sqlResults = myDB.select("SELECT range,keyword FROM [" + table + "] WHERE show_id = ? ", [self.show_id])
if not sqlResults or not len(sqlResults):
return ([], {})

File diff suppressed because it is too large Load diff

View file

@ -229,20 +229,20 @@ class DBConnection(object):
genParams = lambda myDict: [x + " = ?" for x in myDict.keys()]
query = "UPDATE " + tableName + " SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(
query = "UPDATE [" + tableName + "] SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(
genParams(keyDict))
self.action(query, valueDict.values() + keyDict.values())
if self.connection.total_changes == changesBefore:
query = "INSERT INTO " + tableName + " (" + ", ".join(valueDict.keys() + keyDict.keys()) + ")" + \
query = "INSERT INTO [" + tableName + "] (" + ", ".join(valueDict.keys() + keyDict.keys()) + ")" + \
" VALUES (" + ", ".join(["?"] * len(valueDict.keys() + keyDict.keys())) + ")"
self.action(query, valueDict.values() + keyDict.values())
def tableInfo(self, tableName):
# FIXME ? binding is not supported here, but I cannot find a way to escape a string manually
sqlResult = self.select("PRAGMA table_info(%s)" % tableName)
sqlResult = self.select("PRAGMA table_info([%s])" % tableName)
columns = {}
for column in sqlResult:
columns[column['name']] = {'type': column['type']}
@ -261,9 +261,17 @@ class DBConnection(object):
def hasColumn(self, tableName, column):
return column in self.tableInfo(tableName)
def hasIndex(self, tableName, index):
sqlResults = self.select('PRAGMA index_list([%s])' % tableName)
for result in sqlResults:
if result['name'] == index:
return True
return False
def addColumn(self, table, column, type="NUMERIC", default=0):
self.action("ALTER TABLE %s ADD %s %s" % (table, column, type))
self.action("UPDATE %s SET %s = ?" % (table, column), (default,))
self.action("ALTER TABLE [%s] ADD %s %s" % (table, column, type))
self.action("UPDATE [%s] SET %s = ?" % (table, column), (default,))
def close(self):
"""Close database connection"""
@ -353,8 +361,71 @@ class SchemaUpgrade(object):
return column in self.connection.tableInfo(tableName)
def addColumn(self, table, column, type="NUMERIC", default=0):
self.connection.action("ALTER TABLE %s ADD %s %s" % (table, column, type))
self.connection.action("UPDATE %s SET %s = ?" % (table, column), (default,))
self.connection.action("ALTER TABLE [%s] ADD %s %s" % (table, column, type))
self.connection.action("UPDATE [%s] SET %s = ?" % (table, column), (default,))
def dropColumn(self, table, column):
# get old table columns and store the ones we want to keep
result = self.connection.select('pragma table_info([%s])' % table)
keptColumns = [c for c in result if c['name'] != column]
keptColumnsNames = []
final = []
pk = []
# copy the old table schema, column by column
for column in keptColumns:
keptColumnsNames.append(column['name'])
cl = []
cl.append(column['name'])
cl.append(column['type'])
'''
To be implemented if ever required
if column['dflt_value']:
cl.append(str(column['dflt_value']))
if column['notnull']:
cl.append(column['notnull'])
'''
if int(column['pk']) != 0:
pk.append(column['name'])
b = ' '.join(cl)
final.append(b)
# join all the table column creation fields
final = ', '.join(final)
keptColumnsNames = ', '.join(keptColumnsNames)
# generate sql for the new table creation
if len(pk) == 0:
sql = 'CREATE TABLE [%s_new] (%s)' % (table, final)
else:
pk = ', '.join(pk)
sql = 'CREATE TABLE [%s_new] (%s, PRIMARY KEY(%s))' % (table, final, pk)
# create new temporary table and copy the old table data across, barring the removed column
self.connection.action(sql)
self.connection.action('INSERT INTO [%s_new] SELECT %s FROM [%s]' % (table, keptColumnsNames, table))
# copy the old indexes from the old table
result = self.connection.select('SELECT sql FROM sqlite_master WHERE tbl_name=? and type="index"', [table])
# remove the old table and rename the new table to take it's place
self.connection.action('DROP TABLE [%s]' % table)
self.connection.action('ALTER TABLE [%s_new] RENAME TO [%s]' % (table, table))
# write any indexes to the new table
if len(result) > 0:
for index in result:
self.connection.action(index['sql'])
# vacuum the db as we will have a lot of space to reclaim after dropping tables
self.connection.action("VACUUM")
def checkDBVersion(self):
return self.connection.checkDBVersion()
@ -363,3 +434,82 @@ class SchemaUpgrade(object):
new_version = self.checkDBVersion() + 1
self.connection.action("UPDATE db_version SET db_version = ?", [new_version])
return new_version
def setDBVersion(self, new_version):
self.connection.action("UPDATE db_version SET db_version = ?", [new_version])
return new_version
def MigrationCode(myDB):
schema = {
0: sickbeard.mainDB.InitialSchema, # 0->20000
9: sickbeard.mainDB.AddSizeAndSceneNameFields,
10: sickbeard.mainDB.RenameSeasonFolders,
11: sickbeard.mainDB.Add1080pAndRawHDQualities,
12: sickbeard.mainDB.AddShowidTvdbidIndex,
13: sickbeard.mainDB.AddLastUpdateTVDB,
14: sickbeard.mainDB.AddDBIncreaseTo15,
15: sickbeard.mainDB.AddIMDbInfo,
16: sickbeard.mainDB.AddProperNamingSupport,
17: sickbeard.mainDB.AddEmailSubscriptionTable,
18: sickbeard.mainDB.AddProperSearch,
19: sickbeard.mainDB.AddDvdOrderOption,
20: sickbeard.mainDB.AddSubtitlesSupport,
21: sickbeard.mainDB.ConvertTVShowsToIndexerScheme,
22: sickbeard.mainDB.ConvertTVEpisodesToIndexerScheme,
23: sickbeard.mainDB.ConvertIMDBInfoToIndexerScheme,
24: sickbeard.mainDB.ConvertInfoToIndexerScheme,
25: sickbeard.mainDB.AddArchiveFirstMatchOption,
26: sickbeard.mainDB.AddSceneNumbering,
27: sickbeard.mainDB.ConvertIndexerToInteger,
28: sickbeard.mainDB.AddRequireAndIgnoreWords,
29: sickbeard.mainDB.AddSportsOption,
30: sickbeard.mainDB.AddSceneNumberingToTvEpisodes,
31: sickbeard.mainDB.AddAnimeTVShow,
32: sickbeard.mainDB.AddAbsoluteNumbering,
33: sickbeard.mainDB.AddSceneAbsoluteNumbering,
34: sickbeard.mainDB.AddAnimeBlacklistWhitelist,
35: sickbeard.mainDB.AddSceneAbsoluteNumbering2,
36: sickbeard.mainDB.AddXemRefresh,
37: sickbeard.mainDB.AddSceneToTvShows,
38: sickbeard.mainDB.AddIndexerMapping,
39: sickbeard.mainDB.AddVersionToTvEpisodes,
40: sickbeard.mainDB.BumpDatabaseVersion,
41: sickbeard.mainDB.Migrate41,
10000: sickbeard.mainDB.SickGearDatabaseVersion,
10001: sickbeard.mainDB.RemoveDefaultEpStatusFromTvShows
#20000: sickbeard.mainDB.AddCoolSickGearFeature1,
#20001: sickbeard.mainDB.AddCoolSickGearFeature2,
#20002: sickbeard.mainDB.AddCoolSickGearFeature3,
}
db_version = myDB.checkDBVersion()
logger.log(u'Detected database version: v' + str(db_version), logger.DEBUG)
if not (db_version in schema):
if db_version == sickbeard.mainDB.MAX_DB_VERSION:
logger.log(u'Database schema is up-to-date, no upgrade required')
elif db_version < 10000:
logger.log_error_and_exit(u'SickGear does not currently support upgrading from this database version')
else:
logger.log_error_and_exit(u'Invalid database version')
else:
while db_version < sickbeard.mainDB.MAX_DB_VERSION:
try:
update = schema[db_version](myDB)
db_version = update.execute()
except Exception, e:
myDB.close()
logger.log(u'Failed to update database with error: ' + ex(e) + ' attempting recovery...', logger.ERROR)
if restoreDatabase(db_version):
# initialize the main SB database
logger.log_error_and_exit(u'Successfully restored database version:' + str(db_version))
else:
logger.log_error_and_exit(u'Failed to restore database version:' + str(db_version))

View file

@ -1430,4 +1430,75 @@ def get_size(start_path='.'):
def remove_article(text=''):
return re.sub(r'(?i)/^(?:(?:A(?!\s+to)n?)|The)\s(\w)', r'\1', text)
return re.sub(r'(?i)^(?:(?:A(?!\s+to)n?)|The)\s(\w)', r'\1', text)
def client_host(server_host):
'''Extracted from cherrypy libs
Return the host on which a client can connect to the given listener.'''
if server_host == '0.0.0.0':
# 0.0.0.0 is INADDR_ANY, which should answer on localhost.
return '127.0.0.1'
if server_host in ('::', '::0', '::0.0.0.0'):
# :: is IN6ADDR_ANY, which should answer on localhost.
# ::0 and ::0.0.0.0 are non-canonical but common ways to write
# IN6ADDR_ANY.
return '::1'
return server_host
def wait_for_free_port(host, port):
'''Extracted from cherrypy libs
Wait for the specified port to become free (drop requests).'''
if not host:
raise ValueError("Host values of '' or None are not allowed.")
for trial in range(50):
try:
# we are expecting a free port, so reduce the timeout
check_port(host, port, timeout=0.1)
except IOError:
# Give the old server thread time to free the port.
time.sleep(0.1)
else:
return
raise IOError("Port %r not free on %r" % (port, host))
def check_port(host, port, timeout=1.0):
'''Extracted from cherrypy libs
Raise an error if the given port is not free on the given host.'''
if not host:
raise ValueError("Host values of '' or None are not allowed.")
host = client_host(host)
port = int(port)
import socket
# AF_INET or AF_INET6 socket
# Get the correct address family for our host (allows IPv6 addresses)
try:
info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
socket.SOCK_STREAM)
except socket.gaierror:
if ':' in host:
info = [(socket.AF_INET6, socket.SOCK_STREAM, 0, "", (host, port, 0, 0))]
else:
info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (host, port))]
for res in info:
af, socktype, proto, canonname, sa = res
s = None
try:
s = socket.socket(af, socktype, proto)
# See http://groups.google.com/group/cherrypy-users/
# browse_frm/thread/bbfe5eb39c904fe0
s.settimeout(timeout)
s.connect((host, port))
s.close()
raise IOError("Port %s is in use on %s; perhaps the previous "
"httpserver did not shut down properly." %
(repr(port), repr(host)))
except socket.error:
if s:
s.close()

View file

@ -27,33 +27,113 @@ from sickbeard import common
from sickbeard.exceptions import ex
from sickbeard.encodingKludge import fixStupidEncodings
from sickbeard.notifiers.xbmc import XBMCNotifier
# TODO: switch over to using ElementTree
from xml.dom import minidom
try:
import xml.etree.cElementTree as etree
except ImportError:
import elementtree.ElementTree as etree
class PLEXNotifier(XBMCNotifier):
def _notify_pmc(self, message, title="SickGear", host=None, username=None, password=None, force=False):
class PLEXNotifier:
def _send_to_plex(self, command, host, username=None, password=None):
"""Handles communication to Plex hosts via HTTP API
Args:
command: Dictionary of field/data pairs, encoded via urllib and passed to the legacy xbmcCmds HTTP API
host: Plex host:port
username: Plex API username
password: Plex API password
Returns:
Returns 'OK' for successful commands or False if there was an error
"""
# fill in omitted parameters
if not host:
if sickbeard.PLEX_HOST:
host = sickbeard.PLEX_HOST # Use the default Plex host
else:
logger.log(u"No Plex host specified, check your settings", logger.DEBUG)
return False
if not username:
username = sickbeard.PLEX_USERNAME
if not password:
password = sickbeard.PLEX_PASSWORD
# suppress notifications if the notifier is disabled but the notify options are checked
if not sickbeard.USE_PLEX and not force:
logger.log("Notification for Plex not enabled, skipping this notification", logger.DEBUG)
if not host:
logger.log(u"PLEX: No host specified, check your settings", logger.ERROR)
return False
return self._notify_xbmc(message=message, title=title, host=host, username=username, password=password,
force=True)
for key in command:
if type(command[key]) == unicode:
command[key] = command[key].encode('utf-8')
enc_command = urllib.urlencode(command)
logger.log(u"PLEX: Encoded API command: " + enc_command, logger.DEBUG)
url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command)
try:
req = urllib2.Request(url)
# if we have a password, use authentication
if password:
base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
authheader = "Basic %s" % base64string
req.add_header("Authorization", authheader)
logger.log(u"PLEX: Contacting (with auth header) via url: " + url, logger.DEBUG)
else:
logger.log(u"PLEX: Contacting via url: " + url, logger.DEBUG)
response = urllib2.urlopen(req)
result = response.read().decode(sickbeard.SYS_ENCODING)
response.close()
logger.log(u"PLEX: HTTP response: " + result.replace('\n', ''), logger.DEBUG)
# could return result response = re.compile('<html><li>(.+\w)</html>').findall(result)
return 'OK'
except (urllib2.URLError, IOError), e:
logger.log(u"PLEX: Warning: Couldn't contact Plex at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING)
return False
def _notify(self, message, title="SickGear", host=None, username=None, password=None, force=False):
"""Internal wrapper for the notify_snatch and notify_download functions
Args:
message: Message body of the notice to send
title: Title of the notice to send
host: Plex Media Client(s) host:port
username: Plex username
password: Plex password
force: Used for the Test method to override config safety checks
Returns:
Returns a list results in the format of host:ip:result
The result will either be 'OK' or False, this is used to be parsed by the calling function.
"""
# suppress notifications if the notifier is disabled but the notify options are checked
if not sickbeard.USE_PLEX and not force:
return False
# fill in omitted parameters
if not host:
host = sickbeard.PLEX_HOST
if not username:
username = sickbeard.PLEX_USERNAME
if not password:
password = sickbeard.PLEX_PASSWORD
result = ''
for curHost in [x.strip() for x in host.split(",")]:
logger.log(u"PLEX: Sending notification to '" + curHost + "' - " + message, logger.MESSAGE)
command = {'command': 'ExecBuiltIn', 'parameter': 'Notification(' + title.encode("utf-8") + ',' + message.encode("utf-8") + ')'}
notifyResult = self._send_to_plex(command, curHost, username, password)
if notifyResult:
result += curHost + ':' + str(notifyResult)
return result
##############################################################################
# Public functions
##############################################################################
def notify_snatch(self, ep_name):
if sickbeard.PLEX_NOTIFY_ONSNATCH:
@ -74,10 +154,9 @@ class PLEXNotifier(XBMCNotifier):
self._notify_pmc(update_text + new_version, title)
def test_notify(self, host, username, password):
return self._notify_pmc("Testing Plex notifications from SickGear", "Test Notification", host, username,
password, force=True)
return self._notify("This is a test notification from SickGear", "Test", host, username, password, force=True)
def update_library(self):
def update_library(self, ep_obj=None, host=None, username=None, password=None):
"""Handles updating the Plex Media Server host via HTTP API
Plex Media Server currently only supports updating the whole video library and not a specific path.
@ -87,36 +166,68 @@ class PLEXNotifier(XBMCNotifier):
"""
# fill in omitted parameters
if not host:
host = sickbeard.PLEX_SERVER_HOST
if not username:
username = sickbeard.PLEX_USERNAME
if not password:
password = sickbeard.PLEX_PASSWORD
if sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY:
if not sickbeard.PLEX_SERVER_HOST:
logger.log(u"No Plex Media Server host specified, check your settings", logger.DEBUG)
logger.log(u"PLEX: No Plex Media Server host specified, check your settings", logger.DEBUG)
return False
logger.log(u"Updating library for the Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST,
logger.MESSAGE)
logger.log(u"PLEX: Updating library for the Plex Media Server host: " + host, logger.MESSAGE)
url = "http://%s/library/sections" % sickbeard.PLEX_SERVER_HOST
# if username and password were provided, fetch the auth token from plex.tv
token_arg = ""
if username and password:
logger.log(u"PLEX: fetching credentials for Plex user: " + username, logger.DEBUG)
req = urllib2.Request("https://plex.tv/users/sign_in.xml", data="")
base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
authheader = "Basic %s" % base64string
req.add_header("Authorization", authheader)
req.add_header("X-Plex-Client-Identifier", "Sick-Beard-Notifier")
try:
response = urllib2.urlopen(req)
except urllib2.URLError, e:
logger.log(u"PLEX: Error fetching credentials from from plex.tv for user %s: %s" % (username, ex(e)), logger.MESSAGE)
return False
try:
auth_tree = etree.parse(response)
token = auth_tree.findall(".//authentication-token")[0].text
token_arg = "?X-Plex-Token=" + token
except (ValueError, IndexError) as e:
logger.log(u"PLEX: Error parsing plex.tv response: " + ex(e), logger.MESSAGE)
return False
url = "http://%s/library/sections%s" % (sickbeard.PLEX_SERVER_HOST, token_arg)
try:
xml_sections = minidom.parse(urllib.urlopen(url))
xml_tree = etree.parse(urllib.urlopen(url))
media_container = xml_tree.getroot()
except IOError, e:
logger.log(u"Error while trying to contact Plex Media Server: " + ex(e), logger.ERROR)
logger.log(u"PLEX: Error while trying to contact Plex Media Server: " + ex(e), logger.ERROR)
return False
sections = xml_sections.getElementsByTagName('Directory')
sections = media_container.findall('.//Directory')
if not sections:
logger.log(u"Plex Media Server not running on: " + sickbeard.PLEX_SERVER_HOST, logger.MESSAGE)
logger.log(u"PLEX: Plex Media Server not running on: " + sickbeard.PLEX_SERVER_HOST, logger.MESSAGE)
return False
for s in sections:
if s.getAttribute('type') == "show":
url = "http://%s/library/sections/%s/refresh" % (sickbeard.PLEX_SERVER_HOST, s.getAttribute('key'))
for section in sections:
if section.attrib['type'] == "show":
url = "http://%s/library/sections/%s/refresh%s" % (sickbeard.PLEX_SERVER_HOST, section.attrib['key'], token_arg)
try:
urllib.urlopen(url)
except Exception, e:
logger.log(u"Error updating library section for Plex Media Server: " + ex(e), logger.ERROR)
logger.log(u"PLEX: Error updating library section for Plex Media Server: " + ex(e), logger.ERROR)
return False
return True
notifier = PLEXNotifier

View file

@ -61,8 +61,8 @@ class KATProvider(generic.TorrentProvider):
self.cache = KATCache(self)
self.urls = ['http://kickass.to/', 'http://katproxy.com/', 'http://www.kickmirror.com/']
self.url = 'https://kickass.to/'
self.urls = ['http://kickass.so/', 'http://katproxy.com/', 'http://www.kickmirror.com/']
self.url = 'https://kickass.so/'
def isEnabled(self):
return self.enabled

View file

@ -35,10 +35,10 @@ from sickbeard.helpers import sanitizeSceneName
class TorrentDayProvider(generic.TorrentProvider):
urls = {'base_url': 'http://www.torrentday.com',
'login': 'http://www.torrentday.com/torrents/',
'search': 'http://www.torrentday.com/V3/API/API.php',
'download': 'http://www.torrentday.com/download.php/%s/%s'
urls = {'base_url': 'https://torrentday.eu',
'login': 'https://torrentday.eu/torrents/',
'search': 'https://torrentday.eu/V3/API/API.php',
'download': 'https://torrentday.eu/download.php/%s/%s'
}
def __init__(self):

View file

@ -59,20 +59,20 @@ def filterBadReleases(name, parse=True):
# if any of the bad strings are in the name then say no
if sickbeard.IGNORE_WORDS:
resultFilters.extend(sickbeard.IGNORE_WORDS.split(','))
filters = [re.compile('(^|[\W_])%s($|[\W_])' % filter.strip(), re.I) for filter in resultFilters]
filters = [re.compile('(^|[\W_])%s($|[\W_])' % re.escape(filter.strip()), re.I) for filter in resultFilters]
for regfilter in filters:
if regfilter.search(name):
logger.log(u"Invalid scene release: " + name + " contains pattern: " + regfilter.pattern + ", ignoring it",
logger.log(u"Invalid scene release: " + name + " contained: " + regfilter.pattern + ", ignoring it",
logger.DEBUG)
return False
# if any of the good strings aren't in the name then say no
if sickbeard.REQUIRE_WORDS:
require_words = sickbeard.REQUIRE_WORDS.split(',')
filters = [re.compile('(^|[\W_])%s($|[\W_])' % filter.strip(), re.I) for filter in require_words]
filters = [re.compile('(^|[\W_])%s($|[\W_])' % re.escape(filter.strip()), re.I) for filter in require_words]
for regfilter in filters:
if not regfilter.search(name):
logger.log(u"Invalid scene release: " + name + " doesn't contain pattern: " + regfilter.pattern + ", ignoring it",
logger.log(u"Invalid scene release: " + name + " didn't contain: " + regfilter.pattern + ", ignoring it",
logger.DEBUG)
return False

View file

@ -36,41 +36,44 @@ import itertools
class CacheDBConnection(db.DBConnection):
def __init__(self, providerName):
db.DBConnection.__init__(self, "cache.db")
db.DBConnection.__init__(self, 'cache.db')
# Create the table if it's not already there
try:
if not self.hasTable(providerName):
self.action(
"CREATE TABLE [" + providerName + "] (name TEXT, season NUMERIC, episodes TEXT, indexerid NUMERIC, url TEXT, time NUMERIC, quality TEXT, release_group TEXT)")
else:
'CREATE TABLE [' + providerName + '] (name TEXT, season NUMERIC, episodes TEXT, indexerid NUMERIC, url TEXT, time NUMERIC, quality TEXT, release_group TEXT)')
self.action(
'CREATE UNIQUE INDEX IF NOT EXISTS [idx_' + providerName + '_url] ON [' + providerName + '] (url)')
elif not self.hasIndex(providerName, 'idx_%s_url' % providerName):
sqlResults = self.select(
"SELECT url, COUNT(url) as count FROM [" + providerName + "] GROUP BY url HAVING count > 1")
'SELECT url, COUNT(url) as count FROM [' + providerName + '] GROUP BY url HAVING count > 1')
for cur_dupe in sqlResults:
self.action("DELETE FROM [" + providerName + "] WHERE url = ?", [cur_dupe["url"]])
self.action('DELETE FROM [' + providerName + '] WHERE url = ?', [cur_dupe['url']])
self.action(
'CREATE UNIQUE INDEX IF NOT EXISTS [idx_' + providerName + '_url] ON [' + providerName + '] (url)')
# add unique index to prevent further dupes from happening if one does not exist
self.action("CREATE UNIQUE INDEX IF NOT EXISTS idx_url ON [" + providerName + "] (url)")
# add release_group column to table if missing
if not self.hasColumn(providerName, 'release_group'):
self.addColumn(providerName, 'release_group', "TEXT", "")
self.addColumn(providerName, 'release_group', 'TEXT', '')
# add version column to table if missing
if not self.hasColumn(providerName, 'version'):
self.addColumn(providerName, 'version', "NUMERIC", "-1")
self.addColumn(providerName, 'version', 'NUMERIC', '-1')
except Exception, e:
if str(e) != "table [" + providerName + "] already exists":
if str(e) != 'table [' + providerName + '] already exists':
raise
# Create the table if it's not already there
try:
if not self.hasTable('lastUpdate'):
self.action("CREATE TABLE lastUpdate (provider TEXT, time NUMERIC)")
self.action('CREATE TABLE lastUpdate (provider TEXT, time NUMERIC)')
except Exception, e:
if str(e) != "table lastUpdate already exists":
if str(e) != 'table lastUpdate already exists':
raise
class TVCache():
@ -91,7 +94,7 @@ class TVCache():
def _clearCache(self):
if self.shouldClearCache():
myDB = self._getDB()
myDB.action("DELETE FROM [" + self.providerID + "] WHERE 1")
myDB.action('DELETE FROM [' + self.providerID + '] WHERE 1')
def _get_title_and_url(self, item):
# override this in the provider if daily search has a different data layout to backlog searches
@ -151,22 +154,22 @@ class TVCache():
title = self._translateTitle(title)
url = self._translateLinkURL(url)
logger.log(u"Attempting to add item to cache: " + title, logger.DEBUG)
logger.log(u'Attempting to add item to cache: ' + title, logger.DEBUG)
return self._addCacheEntry(title, url)
else:
logger.log(
u"The data returned from the " + self.provider.name + " feed is incomplete, this result is unusable",
u'The data returned from the ' + self.provider.name + ' feed is incomplete, this result is unusable',
logger.DEBUG)
return None
def _getLastUpdate(self):
myDB = self._getDB()
sqlResults = myDB.select("SELECT time FROM lastUpdate WHERE provider = ?", [self.providerID])
sqlResults = myDB.select('SELECT time FROM lastUpdate WHERE provider = ?', [self.providerID])
if sqlResults:
lastTime = int(sqlResults[0]["time"])
lastTime = int(sqlResults[0]['time'])
if lastTime > int(time.mktime(datetime.datetime.today().timetuple())):
lastTime = 0
else:
@ -176,10 +179,10 @@ class TVCache():
def _getLastSearch(self):
myDB = self._getDB()
sqlResults = myDB.select("SELECT time FROM lastSearch WHERE provider = ?", [self.providerID])
sqlResults = myDB.select('SELECT time FROM lastSearch WHERE provider = ?', [self.providerID])
if sqlResults:
lastTime = int(sqlResults[0]["time"])
lastTime = int(sqlResults[0]['time'])
if lastTime > int(time.mktime(datetime.datetime.today().timetuple())):
lastTime = 0
else:
@ -193,7 +196,7 @@ class TVCache():
toDate = datetime.datetime.today()
myDB = self._getDB()
myDB.upsert("lastUpdate",
myDB.upsert('lastUpdate',
{'time': int(time.mktime(toDate.timetuple()))},
{'provider': self.providerID})
@ -202,7 +205,7 @@ class TVCache():
toDate = datetime.datetime.today()
myDB = self._getDB()
myDB.upsert("lastSearch",
myDB.upsert('lastSearch',
{'time': int(time.mktime(toDate.timetuple()))},
{'provider': self.providerID})
@ -212,7 +215,7 @@ class TVCache():
def shouldUpdate(self):
# if we've updated recently then skip the update
if datetime.datetime.today() - self.lastUpdate < datetime.timedelta(minutes=self.minTime):
logger.log(u"Last update was too soon, using old cache: today()-" + str(self.lastUpdate) + "<" + str(
logger.log(u'Last update was too soon, using old cache: today()-' + str(self.lastUpdate) + '<' + str(
datetime.timedelta(minutes=self.minTime)), logger.DEBUG)
return False
@ -239,10 +242,10 @@ class TVCache():
myParser = NameParser(showObj=showObj, convert=True)
parse_result = myParser.parse(name)
except InvalidNameException:
logger.log(u"Unable to parse the filename " + name + " into a valid episode", logger.DEBUG)
logger.log(u'Unable to parse the filename ' + name + ' into a valid episode', logger.DEBUG)
return None
except InvalidShowException:
logger.log(u"Unable to parse the filename " + name + " into a valid show", logger.DEBUG)
logger.log(u'Unable to parse the filename ' + name + ' into a valid show', logger.DEBUG)
return None
if not parse_result or not parse_result.series_name:
@ -254,7 +257,7 @@ class TVCache():
if season and episodes:
# store episodes as a seperated string
episodeText = "|" + "|".join(map(str, episodes)) + "|"
episodeText = '|' + '|'.join(map(str, episodes)) + '|'
# get the current timestamp
curTimestamp = int(time.mktime(datetime.datetime.today().timetuple()))
@ -271,10 +274,10 @@ class TVCache():
# get version
version = parse_result.version
logger.log(u"Added RSS item: [" + name + "] to cache: [" + self.providerID + "]", logger.DEBUG)
logger.log(u'Added RSS item: [' + name + '] to cache: [' + self.providerID + ']', logger.DEBUG)
return [
"INSERT OR IGNORE INTO [" + self.providerID + "] (name, season, episodes, indexerid, url, time, quality, release_group, version) VALUES (?,?,?,?,?,?,?,?,?)",
'INSERT OR IGNORE INTO [' + self.providerID + '] (name, season, episodes, indexerid, url, time, quality, release_group, version) VALUES (?,?,?,?,?,?,?,?,?)',
[name, season, episodeText, parse_result.show.indexerid, url, curTimestamp, quality, release_group, version]]
@ -285,12 +288,12 @@ class TVCache():
else:
return []
def listPropers(self, date=None, delimiter="."):
def listPropers(self, date=None, delimiter='.'):
myDB = self._getDB()
sql = "SELECT * FROM [" + self.providerID + "] WHERE name LIKE '%.PROPER.%' OR name LIKE '%.REPACK.%'"
if date != None:
sql += " AND time >= " + str(int(time.mktime(date.timetuple())))
sql += ' AND time >= ' + str(int(time.mktime(date.timetuple())))
return filter(lambda x: x['indexerid'] != 0, myDB.select(sql))
@ -302,14 +305,14 @@ class TVCache():
myDB = self._getDB()
if type(episode) != list:
sqlResults = myDB.select(
"SELECT * FROM [" + self.providerID + "] WHERE indexerid = ? AND season = ? AND episodes LIKE ?",
[episode.show.indexerid, episode.season, "%|" + str(episode.episode) + "|%"])
'SELECT * FROM [' + self.providerID + '] WHERE indexerid = ? AND season = ? AND episodes LIKE ?',
[episode.show.indexerid, episode.season, '%|' + str(episode.episode) + '|%'])
else:
for epObj in episode:
cl.append([
"SELECT * FROM [" + self.providerID + "] WHERE indexerid = ? AND season = ? AND episodes LIKE ? "
"AND quality IN (" + ",".join([str(x) for x in epObj.wantedQuality]) + ")",
[epObj.show.indexerid, epObj.season, "%|" + str(epObj.episode) + "|%"]])
'SELECT * FROM [' + self.providerID + '] WHERE indexerid = ? AND season = ? AND episodes LIKE ? '
'AND quality IN (' + ','.join([str(x) for x in epObj.wantedQuality]) + ')',
[epObj.show.indexerid, epObj.season, '%|' + str(epObj.episode) + '|%']])
sqlResults = myDB.mass_action(cl, fetchall=True)
sqlResults = list(itertools.chain(*sqlResults))
@ -318,45 +321,45 @@ class TVCache():
for curResult in sqlResults:
# skip non-tv crap
if not show_name_helpers.filterBadReleases(curResult["name"], parse=False):
if not show_name_helpers.filterBadReleases(curResult['name'], parse=False):
continue
# get the show object, or if it's not one of our shows then ignore it
showObj = helpers.findCertainShow(sickbeard.showList, int(curResult["indexerid"]))
showObj = helpers.findCertainShow(sickbeard.showList, int(curResult['indexerid']))
if not showObj:
continue
# skip if provider is anime only and show is not anime
if self.provider.anime_only and not showObj.is_anime:
logger.log(u"" + str(showObj.name) + " is not an anime, skiping", logger.DEBUG)
logger.log(u'' + str(showObj.name) + ' is not an anime, skiping', logger.DEBUG)
continue
# get season and ep data (ignoring multi-eps for now)
curSeason = int(curResult["season"])
curSeason = int(curResult['season'])
if curSeason == -1:
continue
curEp = curResult["episodes"].split("|")[1]
curEp = curResult['episodes'].split('|')[1]
if not curEp:
continue
curEp = int(curEp)
curQuality = int(curResult["quality"])
curReleaseGroup = curResult["release_group"]
curVersion = curResult["version"]
curQuality = int(curResult['quality'])
curReleaseGroup = curResult['release_group']
curVersion = curResult['version']
# if the show says we want that episode then add it to the list
if not showObj.wantEpisode(curSeason, curEp, curQuality, manualSearch):
logger.log(u"Skipping " + curResult["name"] + " because we don't want an episode that's " +
logger.log(u'Skipping ' + curResult['name'] + ' because we don\'t want an episode that\'s ' +
Quality.qualityStrings[curQuality], logger.DEBUG)
continue
epObj = showObj.getEpisode(curSeason, curEp)
# build a result object
title = curResult["name"]
url = curResult["url"]
title = curResult['name']
url = curResult['url']
logger.log(u"Found result " + title + " at " + url)
logger.log(u'Found result ' + title + ' at ' + url)
result = self.provider.getResult([epObj])
result.show = showObj

View file

@ -61,6 +61,7 @@ from sickbeard.scene_numbering import get_scene_numbering, set_scene_numbering,
from sickbeard.blackandwhitelist import BlackAndWhiteList
from browser import WebFileBrowser
from mimetypes import MimeTypes
from lib.dateutil import tz
from lib.unrar2 import RarFile
@ -234,7 +235,9 @@ class MainHandler(RequestHandler):
func = getattr(klass, 'index', None)
if callable(func):
return func(**args)
out = func(**args)
self._headers = klass._headers
return out
raise HTTPError(404)
@ -264,7 +267,7 @@ class MainHandler(RequestHandler):
else:
default_image_name = 'banner.png'
default_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', default_image_name)
image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', default_image_name)
if show and sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)):
cache_obj = image_cache.ImageCache()
@ -279,10 +282,11 @@ class MainHandler(RequestHandler):
image_file_name = cache_obj.banner_thumb_path(show)
if ek.ek(os.path.isfile, image_file_name):
with file(image_file_name, 'rb') as img:
return img.read()
image_path = image_file_name
with file(default_image_path, 'rb') as img:
mime_type, encoding = MimeTypes().guess_type(image_path)
self.set_header('Content-Type', mime_type)
with file(image_path, 'rb') as img:
return img.read()
def setHomeLayout(self, layout):
@ -425,25 +429,6 @@ class MainHandler(RequestHandler):
sql_results.sort(sorts[sickbeard.COMING_EPS_SORT])
t = PageTemplate(headers=self.request.headers, file="comingEpisodes.tmpl")
# paused_item = { 'title': '', 'path': 'toggleComingEpsDisplayPaused' }
# paused_item['title'] = 'Hide Paused' if sickbeard.COMING_EPS_DISPLAY_PAUSED else 'Show Paused'
paused_item = {'title': 'View Paused:', 'path': {'': ''}}
paused_item['path'] = {'Hide': 'toggleComingEpsDisplayPaused'} if sickbeard.COMING_EPS_DISPLAY_PAUSED else {
'Show': 'toggleComingEpsDisplayPaused'}
t.submenu = [
{'title': 'Sort by:', 'path': {'Date': 'setComingEpsSort/?sort=date',
'Show': 'setComingEpsSort/?sort=show',
'Network': 'setComingEpsSort/?sort=network',
}},
{'title': 'Layout:', 'path': {'Banner': 'setComingEpsLayout/?layout=banner',
'Poster': 'setComingEpsLayout/?layout=poster',
'List': 'setComingEpsLayout/?layout=list',
'Calendar': 'setComingEpsLayout/?layout=calendar',
}},
paused_item,
]
t.next_week = datetime.datetime.combine(next_week_dt, datetime.time(tzinfo=network_timezones.sb_timezone))
t.today = datetime.datetime.now(network_timezones.sb_timezone)
t.sql_results = sql_results
@ -1467,7 +1452,6 @@ class History(MainHandler):
ConfigMenu = [
{'title': 'General', 'path': 'config/general/'},
{'title': 'Backup/Restore', 'path': 'config/backuprestore/'},
{'title': 'Search Settings', 'path': 'config/search/'},
{'title': 'Search Providers', 'path': 'config/providers/'},
{'title': 'Subtitles Settings', 'path': 'config/subtitles/'},
@ -1544,7 +1528,7 @@ class ConfigGeneral(MainHandler):
update_shows_on_start=None, trash_remove_show=None, trash_rotate_logs=None, update_frequency=None, launch_browser=None, web_username=None,
use_api=None, api_key=None, indexer_default=None, timezone_display=None, cpu_preset=None,
web_password=None, version_notify=None, enable_https=None, https_cert=None, https_key=None,
handle_reverse_proxy=None, sort_article=None, auto_update=None, notify_on_update=None,
handle_reverse_proxy=None, home_search_focus=None, sort_article=None, auto_update=None, notify_on_update=None,
proxy_setting=None, proxy_indexers=None, anon_redirect=None, git_path=None, git_remote=None, calendar_unprotected=None,
fuzzy_dating=None, trim_zero=None, date_preset=None, date_preset_na=None, time_preset=None,
indexer_timeout=None, rootDir=None, theme_name=None):
@ -1563,6 +1547,7 @@ class ConfigGeneral(MainHandler):
sickbeard.TRASH_ROTATE_LOGS = config.checkbox_to_value(trash_rotate_logs)
config.change_UPDATE_FREQUENCY(update_frequency)
sickbeard.LAUNCH_BROWSER = config.checkbox_to_value(launch_browser)
sickbeard.HOME_SEARCH_FOCUS = config.checkbox_to_value(home_search_focus)
sickbeard.SORT_ARTICLE = config.checkbox_to_value(sort_article)
sickbeard.CPU_PRESET = cpu_preset
sickbeard.ANON_REDIRECT = anon_redirect
@ -1632,53 +1617,6 @@ class ConfigGeneral(MainHandler):
redirect("/config/general/")
class ConfigBackupRestore(MainHandler):
def index(self, *args, **kwargs):
t = PageTemplate(headers=self.request.headers, file="config_backuprestore.tmpl")
t.submenu = ConfigMenu
return _munge(t)
def backup(self, backupDir=None):
finalResult = ''
if backupDir:
source = [os.path.join(sickbeard.DATA_DIR, 'sickbeard.db'), sickbeard.CONFIG_FILE]
target = os.path.join(backupDir, 'SickGear-' + time.strftime('%Y%m%d%H%M%S') + '.zip')
if helpers.makeZip(source, target):
finalResult += "Successful backup to " + target
else:
finalResult += "Backup FAILED"
else:
finalResult += "You need to choose a folder to save your backup to!"
finalResult += "<br />\n"
return finalResult
def restore(self, backupFile=None):
finalResult = ''
if backupFile:
source = backupFile
target_dir = os.path.join(sickbeard.DATA_DIR, 'restore')
if helpers.extractZip(source, target_dir):
finalResult += "Successfully extracted restore files to " + target_dir
finalResult += "<br>Restart SickGear to complete the restore."
else:
finalResult += "Restore FAILED"
else:
finalResult += "You need to select a backup file to restore!"
finalResult += "<br />\n"
return finalResult
class ConfigSearch(MainHandler):
def index(self, *args, **kwargs):
@ -2701,7 +2639,6 @@ class Config(MainHandler):
# map class names to urls
general = ConfigGeneral
backuprestore = ConfigBackupRestore
search = ConfigSearch
providers = ConfigProviders
subtitles = ConfigSubtitles
@ -2873,7 +2810,7 @@ class NewHomeAddShows(MainHandler):
cur_dir = {
'dir': cur_path,
'display_dir': '<b>' + ek.ek(os.path.dirname, cur_path) + os.sep + '</b>' + ek.ek(
'display_dir': '<span class="filepath">' + ek.ek(os.path.dirname, cur_path) + os.sep + '</span>' + ek.ek(
os.path.basename,
cur_path),
}
@ -2890,6 +2827,9 @@ class NewHomeAddShows(MainHandler):
indexer_id = show_name = indexer = None
for cur_provider in sickbeard.metadata_provider_dict.values():
if indexer_id and show_name:
continue
(indexer_id, show_name, indexer) = cur_provider.retrieveShowMetadata(cur_path)
# default to TVDB if indexer was not detected
@ -2917,6 +2857,10 @@ class NewHomeAddShows(MainHandler):
Display the new show page which collects a tvdb id, folder, and extra options and
posts them to addNewShow
"""
self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate')
self.set_header('Pragma', 'no-cache')
self.set_header('Expires', '0')
t = PageTemplate(headers=self.request.headers, file="home_newShow.tmpl")
t.submenu = HomeMenu()
@ -2962,6 +2906,10 @@ class NewHomeAddShows(MainHandler):
Display the new show page which collects a tvdb id, folder, and extra options and
posts them to addNewShow
"""
self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate')
self.set_header('Pragma', 'no-cache')
self.set_header('Expires', '0')
t = PageTemplate(headers=self.request.headers, file="home_recommendedShows.tmpl")
t.submenu = HomeMenu()
@ -2983,11 +2931,21 @@ class NewHomeAddShows(MainHandler):
return
map(final_results.append,
([int(show['tvdb_id'] or 0) if sickbeard.TRAKT_DEFAULT_INDEXER == 1 else int(show['tvdb_id'] or 0),
show['url'], show['title'], show['overview'],
datetime.date.fromtimestamp(int(show['first_aired']) / 1000.0).strftime('%Y%m%d')] for show in
recommendedlist if not helpers.findCertainShow(sickbeard.showList, indexerid=int(show['tvdb_id']))))
([show['url'],
show['title'],
show['overview'],
sbdatetime.sbdatetime.sbfdate(datetime.date.fromtimestamp(int(show['first_aired']))),
sickbeard.indexerApi(1).name,
sickbeard.indexerApi(1).config['icon'],
int(show['tvdb_id'] or 0),
'%s%s' % (sickbeard.indexerApi(1).config['show_url'], int(show['tvdb_id'] or 0)),
sickbeard.indexerApi(2).name,
sickbeard.indexerApi(2).config['icon'],
int(show['tvrage_id'] or 0),
'%s%s' % (sickbeard.indexerApi(2).config['show_url'], int(show['tvrage_id'] or 0))
] for show in recommendedlist if not helpers.findCertainShow(sickbeard.showList, indexerid=int(show['tvdb_id']))))
self.set_header('Content-Type', 'application/json')
return json.dumps({'results': final_results})
def addRecommendedShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None,
@ -3771,10 +3729,17 @@ class Home(MainHandler):
shows.append(show)
t.sortedShowLists = [["Shows", sorted(shows, lambda x, y: cmp(titler(x.name), titler(y.name)))],
["Anime", sorted(anime, lambda x, y: cmp(titler(x.name), titler(y.name)))]]
else:
t.sortedShowLists = [
["Shows", sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name)))]]
tvshows = []
for tvshow_types in t.sortedShowLists:
for tvshow in tvshow_types[1]:
tvshows.append(tvshow.indexerid)
t.tvshow_id_csv = ','.join(str(x) for x in tvshows)
t.bwl = None
if showObj.is_anime:
t.bwl = BlackAndWhiteList(showObj.indexerid)

View file

@ -136,6 +136,4 @@ class WebServer(threading.Thread):
def shutDown(self):
self.alive = False
if self.server:
self.server.stop()
self.io_loop.stop()
self.io_loop.stop()

View file

@ -29,7 +29,7 @@ class DBBasicTests(test.SickbeardTestDBCase):
def test_select(self):
self.db.select("SELECT * FROM tv_episodes WHERE showid = ? AND location != ''", [0000])
self.db.close()
if __name__ == '__main__':
print "=================="

68
tests/migration_tests.py Normal file
View file

@ -0,0 +1,68 @@
import sys
import os.path
import glob
import unittest
import test_lib as test
import sickbeard
from time import sleep
from sickbeard import db
sys.path.append(os.path.abspath('..'))
sys.path.append(os.path.abspath('../lib'))
sickbeard.SYS_ENCODING = 'UTF-8'
class MigrationBasicTests(test.SickbeardTestDBCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_migrations(self):
schema = {
0: sickbeard.mainDB.InitialSchema,
31: sickbeard.mainDB.AddAnimeTVShow,
32: sickbeard.mainDB.AddAbsoluteNumbering,
33: sickbeard.mainDB.AddSceneAbsoluteNumbering,
34: sickbeard.mainDB.AddAnimeBlacklistWhitelist,
35: sickbeard.mainDB.AddSceneAbsoluteNumbering2,
36: sickbeard.mainDB.AddXemRefresh,
37: sickbeard.mainDB.AddSceneToTvShows,
38: sickbeard.mainDB.AddIndexerMapping,
39: sickbeard.mainDB.AddVersionToTvEpisodes,
41: AddDefaultEpStatusToTvShows,
}
count = 1
while count < len(schema.keys()):
myDB = db.DBConnection()
for version in sorted(schema.keys())[:count]:
update = schema[version](myDB)
update.execute()
sleep(0.1)
db.MigrationCode(myDB)
myDB.close()
for filename in glob.glob(os.path.join(test.TESTDIR, test.TESTDBNAME) +'*'):
os.remove(filename)
sleep(0.1)
count += 1
class AddDefaultEpStatusToTvShows(db.SchemaUpgrade):
def execute(self):
self.addColumn("tv_shows", "default_ep_status", "TEXT", "")
self.setDBVersion(41)
if __name__ == '__main__':
print "=================="
print "STARTING - MIGRATION TESTS"
print "=================="
print "######################################################################"
suite = unittest.TestLoader().loadTestsFromTestCase(MigrationBasicTests)
unittest.TextTestRunner(verbosity=2).run(suite)

View file

@ -20,11 +20,11 @@
from __future__ import with_statement
import unittest
import sqlite3
import glob
import sys
import os.path
sys.path.append(os.path.abspath('..'))
sys.path.append(os.path.abspath('../lib'))
@ -34,7 +34,7 @@ import shutil
from sickbeard import encodingKludge as ek, providers, tvcache
from sickbeard import db
from sickbeard.databases import mainDB
from sickbeard.databases import cache_db
from sickbeard.databases import cache_db, failed_db
#=================
# test globals
@ -42,7 +42,7 @@ from sickbeard.databases import cache_db
TESTDIR = os.path.abspath('.')
TESTDBNAME = "sickbeard.db"
TESTCACHEDBNAME = "cache.db"
TESTFAILEDDBNAME = "failed.db"
SHOWNAME = u"show name"
SEASON = 4
@ -102,6 +102,7 @@ createTestCacheFolder()
#=================
def _dummy_saveConfig():
return True
# this overrides the sickbeard save_config which gets called during a db upgrade
# this might be considered a hack
mainDB.sickbeard.save_config = _dummy_saveConfig
@ -165,7 +166,6 @@ class TestCacheDBConnection(TestDBConnection, object):
sickbeard.db.DBConnection = TestDBConnection
sickbeard.tvcache.CacheDBConnection = TestCacheDBConnection
#=================
# test functions
#=================
@ -173,24 +173,31 @@ def setUp_test_db():
"""upgrades the db to the latest version
"""
# upgrading the db
db.upgradeDatabase(db.DBConnection(), mainDB.InitialSchema)
db.MigrationCode(db.DBConnection())
# fix up any db problems
db.sanityCheckDatabase(db.DBConnection(), mainDB.MainSanityCheck)
#and for cache.b too
# and for cachedb too
db.upgradeDatabase(db.DBConnection("cache.db"), cache_db.InitialSchema)
# and for faileddb too
db.upgradeDatabase(db.DBConnection("failed.db"), failed_db.InitialSchema)
def tearDown_test_db():
"""Deletes the test db
although this seams not to work on my system it leaves me with an zero kb file
"""
# uncomment next line so leave the db intact between test and at the end
return False
if os.path.exists(os.path.join(TESTDIR, TESTDBNAME)):
os.remove(os.path.join(TESTDIR, TESTDBNAME))
#return False
for filename in glob.glob(os.path.join(TESTDIR, TESTDBNAME) + '*'):
os.remove(filename)
if os.path.exists(os.path.join(TESTDIR, TESTCACHEDBNAME)):
os.remove(os.path.join(TESTDIR, TESTCACHEDBNAME))
if os.path.exists(os.path.join(TESTDIR, TESTFAILEDDBNAME)):
os.remove(os.path.join(TESTDIR, TESTFAILEDDBNAME))
def setUp_test_episode_file():

View file

@ -1,96 +0,0 @@
import subprocess, os, time, sys, os.path, shutil, re
try:
log_file = open('sb-update.log', 'w')
except:
print "Unable to open sb-update.log, not saving output"
log_file = None
def log(string):
if log_file:
log_file.write(string+'\n')
print string
def isProcRunning(pid):
"""See if a pid is running or not"""
tasklist_cmd = 'tasklist /FI "PID eq '+str(pid)+'" /FO CSV'
p = subprocess.Popen(tasklist_cmd, stdout=subprocess.PIPE)
out, err = p.communicate()
results = out.split('\r\n')
regex = '".*\\.exe","'+str(pid)+'",("[^"]*",?){3}'
for cur_line in results:
if re.match(regex, cur_line, re.I):
return True
return False
if len(sys.argv) < 3:
log("Invalid call.")
sys.exit()
try:
# this should be retrieved from sys.args
pid = sys.argv[1]
# process to re-launch
sb_executable = sys.argv[2:]
sb_closed = False
# try 15 times to make sure it's closed
for i in range(15):
isRunning = isProcRunning(pid)
if isRunning:
time.sleep(5)
continue
else:
sb_closed = True
break
if not sb_closed:
log("SickGear didn't close, unable to update. You'll have to manually restart it.")
sys.exit()
sb_root = os.path.dirname(sb_executable[0])
sb_update_dir = os.path.join(sb_root, 'sb-update')
# do the update if applicable
if os.path.isdir(sb_update_dir):
# find update dir name
update_dir_contents = os.listdir(sb_update_dir)
if len(update_dir_contents) != 1:
log("Invalid update data, update failed.")
sys.exit()
content_dir = os.path.join(sb_update_dir, update_dir_contents[0])
# copy everything from sb_update_dir to sb_root
for dirname, dirnames, filenames in os.walk(content_dir):
dirname = dirname[len(content_dir)+1:]
for curfile in filenames:
if curfile == 'updater.exe':
continue
old_path = os.path.join(content_dir, dirname, curfile)
new_path = os.path.join(sb_root, dirname, curfile)
if os.path.isfile(new_path):
os.remove(new_path)
os.renames(old_path, new_path)
if os.path.isdir(sb_update_dir):
shutil.rmtree(sb_update_dir)
# re-launch SB
p = subprocess.Popen(sb_executable, cwd=os.getcwd())
except Exception, e:
log("Exception while updating: "+str(e))
raise
if log_file:
log_file.close()