diff --git a/CHANGES.md b/CHANGES.md index 79b28bc5..02708a13 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/SickBeard.py b/SickBeard.py index 96c87f25..d9522c57 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -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 diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index ba720e0e..00000000 --- a/TODO.txt +++ /dev/null @@ -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 diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css index d72fb0cf..b78b3bc9 100644 --- a/gui/slick/css/dark.css +++ b/gui/slick/css/dark.css @@ -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 ========================================================================== */ diff --git a/gui/slick/css/lib/jquery.qtip-2.0.1.min.css b/gui/slick/css/lib/jquery.qtip-2.0.1.min.css deleted file mode 100644 index 584c0719..00000000 --- a/gui/slick/css/lib/jquery.qtip-2.0.1.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! qTip2 v2.0.1 (includes: svg ajax tips modal viewport imagemap ie6 / basic css3) | qtip2.com | Licensed MIT, GPL | Mon Dec 31 2012 14:55:17 */.qtip,.qtip{position:absolute;left:-28000px;top:-28000px;display:none;max-width:600px;min-width:50px;font-size:10.5px;line-height:12px;direction:ltr}.qtip-content{position:relative;padding:5px 9px;overflow:hidden;text-align:left;word-wrap:break-word}.qtip-titlebar{position:relative;padding:5px 35px 5px 10px;overflow:hidden;border-width:0 0 1px;font-weight:700}.qtip-titlebar+.qtip-content{border-top-width:0!important}.qtip-close{position:absolute;right:-9px;top:-9px;cursor:pointer;outline:medium none;border-width:1px;border-style:solid;border-color:transparent}.qtip-titlebar .qtip-close{right:4px;top:50%;margin-top:-9px}* html .qtip-titlebar .qtip-close{top:16px}.qtip-titlebar .ui-icon,.qtip-icon .ui-icon{display:block;text-indent:-1000em;direction:ltr;vertical-align:middle}.qtip-icon,.qtip-icon .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;text-decoration:none}.qtip-icon .ui-icon{width:18px;height:14px;text-align:center;text-indent:0;font:normal bold 10px/13px Tahoma,sans-serif;color:inherit;background:transparent none no-repeat -100em -100em}.qtip-focus{}.qtip-hover{}.qtip-default{border-width:1px;border-style:solid;border-color:#F1D031;background-color:#FFFFA3;color:#555}.qtip-default .qtip-titlebar{background-color:#FFEF93}.qtip-default .qtip-icon{border-color:#CCC;background:#F1F1F1;color:#777}.qtip-default .qtip-titlebar .qtip-close{border-color:#AAA;color:#111}/*! Light tooltip style */.qtip-light{background-color:#fff;border-color:#E2E2E2;color:#454545}.qtip-light .qtip-titlebar{background-color:#f1f1f1}/*! Dark tooltip style */.qtip-dark{background-color:#505050;border-color:#303030;color:#f3f3f3}.qtip-dark .qtip-titlebar{background-color:#404040}.qtip-dark .qtip-icon{border-color:#444}.qtip-dark .qtip-titlebar .ui-state-hover{border-color:#303030}/*! Cream tooltip style */.qtip-cream{background-color:#FBF7AA;border-color:#F9E98E;color:#A27D35}.qtip-cream .qtip-titlebar{background-color:#F0DE7D}.qtip-cream .qtip-close .qtip-icon{background-position:-82px 0}/*! Red tooltip style */.qtip-red{background-color:#F78B83;border-color:#D95252;color:#912323}.qtip-red .qtip-titlebar{background-color:#F06D65}.qtip-red .qtip-close .qtip-icon{background-position:-102px 0}.qtip-red .qtip-icon{border-color:#D95252}.qtip-red .qtip-titlebar .ui-state-hover{border-color:#D95252}/*! Green tooltip style */.qtip-green{background-color:#CAED9E;border-color:#90D93F;color:#3F6219}.qtip-green .qtip-titlebar{background-color:#B0DE78}.qtip-green .qtip-close .qtip-icon{background-position:-42px 0}/*! Blue tooltip style */.qtip-blue{background-color:#E5F6FE;border-color:#ADD9ED;color:#5E99BD}.qtip-blue .qtip-titlebar{background-color:#D0E9F5}.qtip-blue .qtip-close .qtip-icon{background-position:-2px 0}.qtip-shadow{-webkit-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);-moz-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);box-shadow:1px 1px 3px 1px rgba(0,0,0,.15)}.qtip-rounded,.qtip-tipsy,.qtip-bootstrap{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.qtip-youtube{-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 3px #333;-moz-box-shadow:0 0 3px #333;box-shadow:0 0 3px #333;color:#fff;border-width:0;background:#4A4A4A;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4A4A4A),color-stop(100%,black));background-image:-webkit-linear-gradient(top,#4A4A4A 0,black 100%);background-image:-moz-linear-gradient(top,#4A4A4A 0,black 100%);background-image:-ms-linear-gradient(top,#4A4A4A 0,black 100%);background-image:-o-linear-gradient(top,#4A4A4A 0,black 100%)}.qtip-youtube .qtip-titlebar{background-color:#4A4A4A;background-color:rgba(0,0,0,0)}.qtip-youtube .qtip-content{padding:.75em;font:12px arial,sans-serif;filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);-ms-filter:"progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);"}.qtip-youtube .qtip-icon{border-color:#222}.qtip-youtube .qtip-titlebar .ui-state-hover{border-color:#303030}.qtip-jtools{background:#232323;background:rgba(0,0,0,.7);background-image:-webkit-gradient(linear,left top,left bottom,from(#717171),to(#232323));background-image:-moz-linear-gradient(top,#717171,#232323);background-image:-webkit-linear-gradient(top,#717171,#232323);background-image:-ms-linear-gradient(top,#717171,#232323);background-image:-o-linear-gradient(top,#717171,#232323);border:2px solid #ddd;border:2px solid rgba(241,241,241,1);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 12px #333;-moz-box-shadow:0 0 12px #333;box-shadow:0 0 12px #333}.qtip-jtools .qtip-titlebar{background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A)"}.qtip-jtools .qtip-content{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323)"}.qtip-jtools .qtip-titlebar,.qtip-jtools .qtip-content{background:transparent;color:#fff;border:0 dashed transparent}.qtip-jtools .qtip-icon{border-color:#555}.qtip-jtools .qtip-titlebar .ui-state-hover{border-color:#333}.qtip-cluetip{-webkit-box-shadow:4px 4px 5px rgba(0,0,0,.4);-moz-box-shadow:4px 4px 5px rgba(0,0,0,.4);box-shadow:4px 4px 5px rgba(0,0,0,.4);background-color:#D9D9C2;color:#111;border:0 dashed transparent}.qtip-cluetip .qtip-titlebar{background-color:#87876A;color:#fff;border:0 dashed transparent}.qtip-cluetip .qtip-icon{border-color:#808064}.qtip-cluetip .qtip-titlebar .ui-state-hover{border-color:#696952;color:#696952}.qtip-tipsy{background:#000;background:rgba(0,0,0,.87);color:#fff;border:0 solid transparent;font-size:11px;font-family:'Lucida Grande',sans-serif;font-weight:700;line-height:16px;text-shadow:0 1px black}.qtip-tipsy .qtip-titlebar{padding:6px 35px 0 10;background-color:transparent}.qtip-tipsy .qtip-content{padding:6px 10}.qtip-tipsy .qtip-icon{border-color:#222;text-shadow:none}.qtip-tipsy .qtip-titlebar .ui-state-hover{border-color:#303030}.qtip-tipped{border:3px solid #959FA9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-color:#F9F9F9;color:#454545;font-weight:400;font-family:serif}.qtip-tipped .qtip-titlebar{border-bottom-width:0;color:#fff;background:#3A79B8;background-image:-webkit-gradient(linear,left top,left bottom,from(#3A79B8),to(#2E629D));background-image:-webkit-linear-gradient(top,#3A79B8,#2E629D);background-image:-moz-linear-gradient(top,#3A79B8,#2E629D);background-image:-ms-linear-gradient(top,#3A79B8,#2E629D);background-image:-o-linear-gradient(top,#3A79B8,#2E629D);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D)"}.qtip-tipped .qtip-icon{border:2px solid #285589;background:#285589}.qtip-tipped .qtip-icon .ui-icon{background-color:#FBFBFB;color:#555}.qtip-bootstrap{font-size:14px;line-height:20px;color:#333;padding:1px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.qtip-bootstrap .qtip-titlebar{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.qtip-bootstrap .qtip-titlebar .qtip-close{right:11px;top:45%;border-style:none}.qtip-bootstrap .qtip-content{padding:9px 14px}.qtip-bootstrap .qtip-icon{background:transparent}.qtip-bootstrap .qtip-icon .ui-icon{width:auto;height:auto;float:right;font-size:20px;font-weight:700;line-height:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.qtip-bootstrap .qtip-icon .ui-icon:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}.qtip:not(.ie9haxors) div.qtip-content,.qtip:not(.ie9haxors) div.qtip-titlebar{filter:none;-ms-filter:none}.qtip .qtip-tip{margin:0 auto;overflow:hidden;z-index:10}.qtip .qtip-tip,.qtip .qtip-tip .qtip-vml{position:absolute;color:#123456;background:transparent;border:0 dashed transparent}.qtip .qtip-tip canvas{top:0;left:0}.qtip .qtip-tip .qtip-vml{behavior:url(#default#VML);display:inline-block;visibility:visible}#qtip-overlay{position:fixed;left:-10000em;top:-10000em}#qtip-overlay.blurs{cursor:pointer}#qtip-overlay div{position:absolute;left:0;top:0;width:100%;height:100%;background-color:#000;opacity:.7;filter:alpha(opacity=70);-ms-filter:"alpha(Opacity=70)"}.qtipmodal-ie6fix{position:absolute!important} \ No newline at end of file diff --git a/gui/slick/css/lib/jquery.qtip-2.2.1.min.css b/gui/slick/css/lib/jquery.qtip-2.2.1.min.css new file mode 100644 index 00000000..ac501ed1 --- /dev/null +++ b/gui/slick/css/lib/jquery.qtip-2.2.1.min.css @@ -0,0 +1,3 @@ +/* qTip2 v2.2.1 | Plugins: tips viewport imagemap svg modal ie6 | Styles: core basic css3 | qtip2.com | Licensed MIT | Sat Sep 06 2014 18:25:07 */ + +.qtip{position:absolute;left:-28000px;top:-28000px;display:none;max-width:280px;min-width:50px;font-size:10.5px;line-height:12px;direction:ltr;box-shadow:none;padding:0}.qtip-content{position:relative;padding:5px 9px;overflow:hidden;text-align:left;word-wrap:break-word}.qtip-titlebar{position:relative;padding:5px 35px 5px 10px;overflow:hidden;border-width:0 0 1px;font-weight:700}.qtip-titlebar+.qtip-content{border-top-width:0!important}.qtip-close{position:absolute;right:-9px;top:-9px;z-index:11;cursor:pointer;outline:medium none;border:1px solid transparent}.qtip-titlebar .qtip-close{right:4px;top:50%;margin-top:-9px}* html .qtip-titlebar .qtip-close{top:16px}.qtip-titlebar .ui-icon,.qtip-icon .ui-icon{display:block;text-indent:-1000em;direction:ltr}.qtip-icon,.qtip-icon .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;text-decoration:none}.qtip-icon .ui-icon{width:18px;height:14px;line-height:14px;text-align:center;text-indent:0;font:400 bold 10px/13px Tahoma,sans-serif;color:inherit;background:transparent none no-repeat -100em -100em}.qtip-focus{}.qtip-hover{}.qtip-default{border:1px solid #F1D031;background-color:#FFFFA3;color:#555}.qtip-default .qtip-titlebar{background-color:#FFEF93}.qtip-default .qtip-icon{border-color:#CCC;background:#F1F1F1;color:#777}.qtip-default .qtip-titlebar .qtip-close{border-color:#AAA;color:#111} .qtip-light{background-color:#fff;border-color:#E2E2E2;color:#454545}.qtip-light .qtip-titlebar{background-color:#f1f1f1} .qtip-dark{background-color:#505050;border-color:#303030;color:#f3f3f3}.qtip-dark .qtip-titlebar{background-color:#404040}.qtip-dark .qtip-icon{border-color:#444}.qtip-dark .qtip-titlebar .ui-state-hover{border-color:#303030} .qtip-cream{background-color:#FBF7AA;border-color:#F9E98E;color:#A27D35}.qtip-cream .qtip-titlebar{background-color:#F0DE7D}.qtip-cream .qtip-close .qtip-icon{background-position:-82px 0} .qtip-red{background-color:#F78B83;border-color:#D95252;color:#912323}.qtip-red .qtip-titlebar{background-color:#F06D65}.qtip-red .qtip-close .qtip-icon{background-position:-102px 0}.qtip-red .qtip-icon{border-color:#D95252}.qtip-red .qtip-titlebar .ui-state-hover{border-color:#D95252} .qtip-green{background-color:#CAED9E;border-color:#90D93F;color:#3F6219}.qtip-green .qtip-titlebar{background-color:#B0DE78}.qtip-green .qtip-close .qtip-icon{background-position:-42px 0} .qtip-blue{background-color:#E5F6FE;border-color:#ADD9ED;color:#5E99BD}.qtip-blue .qtip-titlebar{background-color:#D0E9F5}.qtip-blue .qtip-close .qtip-icon{background-position:-2px 0}.qtip-shadow{-webkit-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);-moz-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);box-shadow:1px 1px 3px 1px rgba(0,0,0,.15)}.qtip-rounded,.qtip-tipsy,.qtip-bootstrap{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.qtip-rounded .qtip-titlebar{-moz-border-radius:4px 4px 0 0;-webkit-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.qtip-youtube{-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 3px #333;-moz-box-shadow:0 0 3px #333;box-shadow:0 0 3px #333;color:#fff;border:0 solid transparent;background:#4A4A4A;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4A4A4A),color-stop(100%,#000));background-image:-webkit-linear-gradient(top,#4A4A4A 0,#000 100%);background-image:-moz-linear-gradient(top,#4A4A4A 0,#000 100%);background-image:-ms-linear-gradient(top,#4A4A4A 0,#000 100%);background-image:-o-linear-gradient(top,#4A4A4A 0,#000 100%)}.qtip-youtube .qtip-titlebar{background-color:#4A4A4A;background-color:rgba(0,0,0,0)}.qtip-youtube .qtip-content{padding:.75em;font:12px arial,sans-serif;filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);-ms-filter:"progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);"}.qtip-youtube .qtip-icon{border-color:#222}.qtip-youtube .qtip-titlebar .ui-state-hover{border-color:#303030}.qtip-jtools{background:#232323;background:rgba(0,0,0,.7);background-image:-webkit-gradient(linear,left top,left bottom,from(#717171),to(#232323));background-image:-moz-linear-gradient(top,#717171,#232323);background-image:-webkit-linear-gradient(top,#717171,#232323);background-image:-ms-linear-gradient(top,#717171,#232323);background-image:-o-linear-gradient(top,#717171,#232323);border:2px solid #ddd;border:2px solid rgba(241,241,241,1);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 12px #333;-moz-box-shadow:0 0 12px #333;box-shadow:0 0 12px #333}.qtip-jtools .qtip-titlebar{background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A)"}.qtip-jtools .qtip-content{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323)"}.qtip-jtools .qtip-titlebar,.qtip-jtools .qtip-content{background:transparent;color:#fff;border:0 dashed transparent}.qtip-jtools .qtip-icon{border-color:#555}.qtip-jtools .qtip-titlebar .ui-state-hover{border-color:#333}.qtip-cluetip{-webkit-box-shadow:4px 4px 5px rgba(0,0,0,.4);-moz-box-shadow:4px 4px 5px rgba(0,0,0,.4);box-shadow:4px 4px 5px rgba(0,0,0,.4);background-color:#D9D9C2;color:#111;border:0 dashed transparent}.qtip-cluetip .qtip-titlebar{background-color:#87876A;color:#fff;border:0 dashed transparent}.qtip-cluetip .qtip-icon{border-color:#808064}.qtip-cluetip .qtip-titlebar .ui-state-hover{border-color:#696952;color:#696952}.qtip-tipsy{background:#000;background:rgba(0,0,0,.87);color:#fff;border:0 solid transparent;font-size:11px;font-family:'Lucida Grande',sans-serif;font-weight:700;line-height:16px;text-shadow:0 1px #000}.qtip-tipsy .qtip-titlebar{padding:6px 35px 0 10px;background-color:transparent}.qtip-tipsy .qtip-content{padding:6px 10px}.qtip-tipsy .qtip-icon{border-color:#222;text-shadow:none}.qtip-tipsy .qtip-titlebar .ui-state-hover{border-color:#303030}.qtip-tipped{border:3px solid #959FA9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-color:#F9F9F9;color:#454545;font-weight:400;font-family:serif}.qtip-tipped .qtip-titlebar{border-bottom-width:0;color:#fff;background:#3A79B8;background-image:-webkit-gradient(linear,left top,left bottom,from(#3A79B8),to(#2E629D));background-image:-webkit-linear-gradient(top,#3A79B8,#2E629D);background-image:-moz-linear-gradient(top,#3A79B8,#2E629D);background-image:-ms-linear-gradient(top,#3A79B8,#2E629D);background-image:-o-linear-gradient(top,#3A79B8,#2E629D);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D)"}.qtip-tipped .qtip-icon{border:2px solid #285589;background:#285589}.qtip-tipped .qtip-icon .ui-icon{background-color:#FBFBFB;color:#555}.qtip-bootstrap{font-size:14px;line-height:20px;color:#333;padding:1px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.qtip-bootstrap .qtip-titlebar{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.qtip-bootstrap .qtip-titlebar .qtip-close{right:11px;top:45%;border-style:none}.qtip-bootstrap .qtip-content{padding:9px 14px}.qtip-bootstrap .qtip-icon{background:transparent}.qtip-bootstrap .qtip-icon .ui-icon{width:auto;height:auto;float:right;font-size:20px;font-weight:700;line-height:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.qtip-bootstrap .qtip-icon .ui-icon:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}.qtip:not(.ie9haxors) div.qtip-content,.qtip:not(.ie9haxors) div.qtip-titlebar{filter:none;-ms-filter:none}.qtip .qtip-tip{margin:0 auto;overflow:hidden;z-index:10}x:-o-prefocus,.qtip .qtip-tip{visibility:hidden}.qtip .qtip-tip,.qtip .qtip-tip .qtip-vml,.qtip .qtip-tip canvas{position:absolute;color:#123456;background:transparent;border:0 dashed transparent}.qtip .qtip-tip canvas{top:0;left:0}.qtip .qtip-tip .qtip-vml{behavior:url(#default#VML);display:inline-block;visibility:visible}#qtip-overlay{position:fixed;left:0;top:0;width:100%;height:100%}#qtip-overlay.blurs{cursor:pointer}#qtip-overlay div{position:absolute;left:0;top:0;width:100%;height:100%;background-color:#000;opacity:.7;filter:alpha(opacity=70);-ms-filter:"alpha(Opacity=70)"}.qtipmodal-ie6fix{position:absolute!important} \ No newline at end of file diff --git a/gui/slick/css/light.css b/gui/slick/css/light.css index a110113c..416ec481 100644 --- a/gui/slick/css/light.css +++ b/gui/slick/css/light.css @@ -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 ========================================================================== */ diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index 5a3870a8..7c00de52 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -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} diff --git a/gui/slick/images/network/esquire network.png b/gui/slick/images/network/esquire network.png new file mode 100644 index 00000000..78b3af5a Binary files /dev/null and b/gui/slick/images/network/esquire network.png differ diff --git a/gui/slick/images/network/smithsonian channel.png b/gui/slick/images/network/smithsonian channel.png new file mode 100644 index 00000000..4b88af11 Binary files /dev/null and b/gui/slick/images/network/smithsonian channel.png differ diff --git a/gui/slick/images/notifiers/plex.png b/gui/slick/images/notifiers/plex.png index 1d97ad22..f630f9b2 100644 Binary files a/gui/slick/images/notifiers/plex.png and b/gui/slick/images/notifiers/plex.png differ diff --git a/gui/slick/images/providers/6box.png b/gui/slick/images/providers/6box.png index 60eb5858..3e77530b 100644 Binary files a/gui/slick/images/providers/6box.png and b/gui/slick/images/providers/6box.png differ diff --git a/gui/slick/images/providers/newztown.png b/gui/slick/images/providers/newztown.png index 5bdc9fea..56d2be31 100644 Binary files a/gui/slick/images/providers/newztown.png and b/gui/slick/images/providers/newztown.png differ diff --git a/gui/slick/images/providers/nzb_is.png b/gui/slick/images/providers/nzb_is.png new file mode 100644 index 00000000..886a600e Binary files /dev/null and b/gui/slick/images/providers/nzb_is.png differ diff --git a/gui/slick/images/providers/nzbs_in.png b/gui/slick/images/providers/nzbs_in.png new file mode 100644 index 00000000..58c7b7b6 Binary files /dev/null and b/gui/slick/images/providers/nzbs_in.png differ diff --git a/gui/slick/images/providers/nzbs_org.png b/gui/slick/images/providers/nzbs_org.png index d096f667..f0689008 100644 Binary files a/gui/slick/images/providers/nzbs_org.png and b/gui/slick/images/providers/nzbs_org.png differ diff --git a/gui/slick/images/providers/omgwtfnzbs.png b/gui/slick/images/providers/omgwtfnzbs.png index 3de01dcf..ed752797 100644 Binary files a/gui/slick/images/providers/omgwtfnzbs.png and b/gui/slick/images/providers/omgwtfnzbs.png differ diff --git a/gui/slick/images/providers/tokyotoshokan.png b/gui/slick/images/providers/tokyotoshokan.png index 66e8c7bf..51628a61 100644 Binary files a/gui/slick/images/providers/tokyotoshokan.png and b/gui/slick/images/providers/tokyotoshokan.png differ diff --git a/gui/slick/images/providers/womble_s_index.png b/gui/slick/images/providers/womble_s_index.png index 5cb053c6..c4d3a8f1 100644 Binary files a/gui/slick/images/providers/womble_s_index.png and b/gui/slick/images/providers/womble_s_index.png differ diff --git a/gui/slick/interfaces/default/config_backuprestore.tmpl b/gui/slick/interfaces/default/config_backuprestore.tmpl deleted file mode 100644 index 0ce945ab..00000000 --- a/gui/slick/interfaces/default/config_backuprestore.tmpl +++ /dev/null @@ -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") - - - -#if $varExists('header') -

$header

-#else -

$title

-#end if - -#set $indexer = 0 -#if $sickbeard.INDEXER_DEFAULT - #set $indexer = $sickbeard.INDEXER_DEFAULT -#end if - - - -
-
- -
-
- - -
-
-

Backup

-

Backup your main database file and config.

-
- -
-
- Select the folder you wish to save your backup file to: - -

- - - - -
- -
-
-
- -
- -
-
-

Restore

-

Restore your main database file and config.

-
- -
-
- Select the backup file you wish to restore: - -

- - - - -
- -
-
-
-
-
-
-
-
- -
- - - -#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl") diff --git a/gui/slick/interfaces/default/config_general.tmpl b/gui/slick/interfaces/default/config_general.tmpl index 1b2cd342..02e2a74c 100644 --- a/gui/slick/interfaces/default/config_general.tmpl +++ b/gui/slick/interfaces/default/config_general.tmpl @@ -215,6 +215,16 @@ +
+ +
+
Click below to test.
diff --git a/gui/slick/interfaces/default/displayShow.tmpl b/gui/slick/interfaces/default/displayShow.tmpl index 38a40221..72fd5c8d 100644 --- a/gui/slick/interfaces/default/displayShow.tmpl +++ b/gui/slick/interfaces/default/displayShow.tmpl @@ -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}] }); //--> diff --git a/gui/slick/interfaces/default/editShow.tmpl b/gui/slick/interfaces/default/editShow.tmpl index f89f3746..bdfc2735 100644 --- a/gui/slick/interfaces/default/editShow.tmpl +++ b/gui/slick/interfaces/default/editShow.tmpl @@ -65,7 +65,6 @@ Location:

-Quality: #set $qualities = $common.Quality.splitQuality(int($show.quality)) #set global $anyQualities = $qualities[0] #set global $bestQualities = $qualities[1] diff --git a/gui/slick/interfaces/default/home.tmpl b/gui/slick/interfaces/default/home.tmpl index e9b988f0..41ec20cc 100644 --- a/gui/slick/interfaces/default/home.tmpl +++ b/gui/slick/interfaces/default/home.tmpl @@ -49,110 +49,112 @@ #if $varExists('header') -

$header

+

$header

#else -

$title

+

$title

#end if -
- Layout: - +   +
+ Layout: +#else +
+ Layout: +#end if + + - - #if $layout == 'poster': -   - Sort By: - + + + + -   - Sort Order: - + +   - #end if + #end if
-#if $layout != 'poster': -
- -
-#end if #for $curShowlist in $showlists: #set $curListType = $curShowlist[0] @@ -256,11 +265,11 @@
#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:
@@ -269,7 +278,7 @@
#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 -
+
@@ -399,29 +408,28 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
- #if $layout != 'simple': - #if $curShow.network: + #if $layout != 'simple': + #if $curShow.network: $curShow.network - #else: + #else: No Network - #end if - #else: + #end if + #else: $curShow.network - #end if + #end if - #if $curShow.quality in $qualityPresets: + #if $curShow.quality in $qualityPresets: $qualityPresetStrings[$curShow.quality] - #else: + #else: Custom - #end if + #end if
- #end for @@ -432,7 +440,7 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name)) - + @@ -443,37 +451,37 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name)) - - - + + + - - - + + + #for $curLoadingShow in $sickbeard.showQueueScheduler.action.loadingShowList: - #if $curLoadingShow.show != None and $curLoadingShow.show in $sickbeard.showList: - #continue - #end if - - - - - - - - - - + + + + + + + + + + #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 - + - #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)) #else: - + #end if - - #if $layout == 'small': - + + #else if $layout == 'banner': - - #else if $layout == 'simple': - + + #else if $layout == 'simple': + #end if #if $layout != 'simple': - #else: + #else: - #end if + #end if - #if $curShow.quality in $qualityPresets: + #if $curShow.quality in $qualityPresets: - #else: + #else: - #end if - + #end if + - - - - + #end for diff --git a/gui/slick/interfaces/default/home_addExistingShow.tmpl b/gui/slick/interfaces/default/home_addExistingShow.tmpl index 07774da1..e398b54d 100644 --- a/gui/slick/interfaces/default/home_addExistingShow.tmpl +++ b/gui/slick/interfaces/default/home_addExistingShow.tmpl @@ -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") - - +#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') @@ -21,7 +19,7 @@ -#if $varExists('header') +#if $varExists('header')

$header

-#else +#else

$title

#end if -
- -
- #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_rootDirs.tmpl") -
-
- #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_addShowOptions.tmpl") -
-
-
+ -

SickGear can add existing shows, using the current options, by using locally stored NFO/XML metadata to eliminate user interaction.
-If you would rather have SickGear prompt you to customize each show, then use the checkbox below.

+ -

+

Tip: shows are added quicker when usable show nfo and xml metadata is found

-
+

+ + +

-
Displaying folders within these directories which aren't already added to SickGear:
+
+ +
+
+#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_rootDirs.tmpl') +
+
+
+
+#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_addShowOptions.tmpl') +
+
+
- +
+
-
-
-
- +

The following parent folder(s) are scanned for existing shows. Toggle a folder to display shows

+ + + +

shows not known to SickGear are listed below...

+ +
+ +
+ -#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') diff --git a/gui/slick/interfaces/default/home_addShows.tmpl b/gui/slick/interfaces/default/home_addShows.tmpl index 71272921..b82f59b4 100644 --- a/gui/slick/interfaces/default/home_addShows.tmpl +++ b/gui/slick/interfaces/default/home_addShows.tmpl @@ -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')

$header

@@ -18,46 +18,49 @@

$title

#end if -
+
- -
-
-

Add New Show

-

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.

-
-
+ +
+
+

Add New Show

+

Search a TV database for a show to add. A new folder will be created for episodes

+
+
-

- -
-
-

Add Trending Show

-

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.

-
-
+ +
+
+

Add From Trending

+

Browse a current trending show list to add from. A folder for episodes will be created

+
+
-

- #if $sickbeard.USE_TRAKT == True: - -
-
-

Add Recommended Shows

-

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.

-
-
+ +
+
+

Add Existing Shows

+

Scan parent folders for shows and episode metadata to import into SickGear

+
+
-

- #end if - -
-
-

Add Existing Shows

-

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.

-
-
+#if True == $sickbeard.USE_TRAKT: + +
+
+

Add Recommended

+

Browse recommendations based on your Trakt.tv show library to add to SickGear

+
+
+#else +
+

There's more... unlock another button to browse
+ recommended shows based on your Trakt.tv show
+ library by enabling Trakt in Config/Notifications/Social

+
+#end if +
-
+
 
- -#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl") \ No newline at end of file +#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl') \ No newline at end of file diff --git a/gui/slick/interfaces/default/home_massAddTable.tmpl b/gui/slick/interfaces/default/home_massAddTable.tmpl index 920e5695..1f0faf79 100644 --- a/gui/slick/interfaces/default/home_massAddTable.tmpl +++ b/gui/slick/interfaces/default/home_massAddTable.tmpl @@ -2,10 +2,10 @@ #from sickbeard.helpers import anon_url
Next Ep ShowStatus
Add Show
(loading) - #if $curLoadingShow.show == None: - Loading... ($curLoadingShow.show_name) - #else: - $curLoadingShow.show.name + #if $curLoadingShow.show != None and $curLoadingShow.show in $sickbeard.showList: + #continue #end if -
(loading) + #if $curLoadingShow.show == None: + Loading... ($curLoadingShow.show_name) + #else: + $curLoadingShow.show.name + #end if +
$sbdatetime.sbdatetime.sbfdate($ldatetime)
$time.mktime($ldatetime.timetuple())
-
- - $curShow.indexerid - + + #if $layout == 'small': +
+ - - $curShow.name - - $curShow.name + $curShow.name + + $curShow.name #if $curShow.network: - $curShow.network - #else: - No Network - #end if + $curShow.network + $curShow.network + #else: + No Network + #end if - $curShow.network + $curShow.network $qualityPresetStrings[$curShow.quality]Custom$progressbar_percent
+ + \"Y\"" + #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
- + - + @@ -31,7 +31,7 @@ #if $curDir['existing_info'][1] and $indexer > 0: - + #else: #end if diff --git a/gui/slick/interfaces/default/home_newShow.tmpl b/gui/slick/interfaces/default/home_newShow.tmpl index 715f8e90..5b8dd0dc 100644 --- a/gui/slick/interfaces/default/home_newShow.tmpl +++ b/gui/slick/interfaces/default/home_newShow.tmpl @@ -24,15 +24,17 @@

$title

#end if + +
aoeu

-
+ -
- Find a show on the TVDB or TVRAGE +
+

Find show at a TV database

@@ -48,10 +50,10 @@ #else   + * -   +  *   + +
+

* SickGear supports english episodes. The language choice is used for fetching metadata and episode filenames

-
- * This will only affect the language of the retrieved metadata file contents and episode filenames.
- This DOES NOT allow SickGear to download non-english TV episodes!
-
-

+
#end if
+
 
-
- Pick the parent folder +
+

Pick parent folder

-
+
#if $provided_show_dir Pre-chosen Destination Folder: $provided_show_dir

@@ -82,13 +84,15 @@ #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_rootDirs.tmpl') #end if
+
 
-
- Customize options +
+

Set custom options

#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_addShowOptions.tmpl')
+
 
#for $curNextDir in $other_shows diff --git a/gui/slick/interfaces/default/home_recommendedShows.tmpl b/gui/slick/interfaces/default/home_recommendedShows.tmpl index de3afd08..5d5bdccd 100644 --- a/gui/slick/interfaces/default/home_recommendedShows.tmpl +++ b/gui/slick/interfaces/default/home_recommendedShows.tmpl @@ -12,7 +12,6 @@ #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl") - @@ -24,35 +23,40 @@

$title

#end if + +

- + -
- Select a recommended show +
+

Select a recommended show


+
 
-
- Pick the parent folder +
+

Pick parent folder

-
+
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_rootDirs.tmpl")
+
 
-
- Customize options +
+

Set custom options

#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_addShowOptions.tmpl")
+
 
diff --git a/gui/slick/interfaces/default/inc_addShowOptions.tmpl b/gui/slick/interfaces/default/inc_addShowOptions.tmpl index 568346c3..6728a8ca 100644 --- a/gui/slick/interfaces/default/inc_addShowOptions.tmpl +++ b/gui/slick/interfaces/default/inc_addShowOptions.tmpl @@ -2,62 +2,77 @@ #from sickbeard.common import * #from sickbeard import subtitles - #if $sickbeard.USE_SUBTITLES: -
- - -
- #end if - -
- -
- -
- - -
+
+ +
- - -
+

Tip: The following options are editable later in the detail view of the show

+
-
- - -
+#if $sickbeard.USE_SUBTITLES: +
+ +
+#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") - -
-
- -
+
+ +
+ +
+ +
+ +
+ +
+ +#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") + +
+ +
diff --git a/gui/slick/interfaces/default/inc_bottom.tmpl b/gui/slick/interfaces/default/inc_bottom.tmpl index 8ec9cada..1f1b3df5 100644 --- a/gui/slick/interfaces/default/inc_bottom.tmpl +++ b/gui/slick/interfaces/default/inc_bottom.tmpl @@ -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 $shows_total shows ($shows_active active) - | <%= ep_downloaded %><%= ('', ' (+%s snatched)' % str(ep_snatched))[ep_snatched > 0] %> / $ep_total episodes downloaded + | <%= ep_downloaded %> + <%= ( + '',\ + ' (+%s snatched)' % \ + ( + str(ep_snatched), + '%s' % \ + (localRoot, str(ep_snatched)) + )['Episode Overview' != localheader] + )[0 < ep_snatched] + %> +  / $ep_total episodes downloaded | daily search: <%= str(sickbeard.dailySearchScheduler.timeLeft()).split('.')[0] %> | backlog search: $sbdatetime.sbdatetime.sbfdate($sickbeard.backlogSearchScheduler.nextRun()) diff --git a/gui/slick/interfaces/default/inc_qualityChooser.tmpl b/gui/slick/interfaces/default/inc_qualityChooser.tmpl index 5653aeab..8941e253 100644 --- a/gui/slick/interfaces/default/inc_qualityChooser.tmpl +++ b/gui/slick/interfaces/default/inc_qualityChooser.tmpl @@ -2,46 +2,48 @@ #from sickbeard.common import Quality, qualityPresets, qualityPresetStrings
-
-
-

One of the Initial quality selections must be obtained before SickGear will attempt to process the Archive selections.

-
+
+

One of the Initial quality selections must succeed before attempting to process Archive selections.

+
-
-

Initial

- #set $anyQualityList = filter(lambda x: x > $Quality.NONE, $Quality.qualityStrings) - -
+ +
+

Initial

+#set $anyQualityList = filter(lambda x: x > $Quality.NONE, $Quality.qualityStrings) + +
-
-

Archive

- #set $bestQualityList = filter(lambda x: x > $Quality.SDTV and x < $Quality.UNKNOWN, $Quality.qualityStrings) - -
-
+
+

Archive

+#set $bestQualityList = filter(lambda x: x > $Quality.SDTV and x < $Quality.UNKNOWN, $Quality.qualityStrings) + +
+ +
diff --git a/gui/slick/interfaces/default/inc_top.tmpl b/gui/slick/interfaces/default/inc_top.tmpl index 2aa8550b..1c9297b2 100644 --- a/gui/slick/interfaces/default/inc_top.tmpl +++ b/gui/slick/interfaces/default/inc_top.tmpl @@ -7,7 +7,7 @@ - + SickGear - BRANCH:[$sickbeard.BRANCH] - $title - + - +#set $tab = 4 - #if $varExists('submenu'): +#if $varExists('submenu'): - #end if - - #if $sickbeard.NEWEST_VERSION_STRING: +#end if + +#if $sickbeard.NEWEST_VERSION_STRING: - #end if +#end if -
-
+#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)] +
+
diff --git a/gui/slick/interfaces/default/manage_episodeStatuses.tmpl b/gui/slick/interfaces/default/manage_episodeStatuses.tmpl index 78a67891..0cd7844d 100644 --- a/gui/slick/interfaces/default/manage_episodeStatuses.tmpl +++ b/gui/slick/interfaces/default/manage_episodeStatuses.tmpl @@ -40,7 +40,7 @@ Manage episodes with status -

Shows containing $common.statusStrings[$int($whichStatus)] episodes

+

Shows containing $common.statusStrings[$int($whichStatus)] episodes


@@ -65,7 +65,7 @@ $statusList.append($common.FAILED) #end for - +
@@ -76,7 +76,7 @@ $statusList.append($common.FAILED)
DirectoryShow Name (tvshow.nfo)Indexer
Parent\show folderShow name
(tvshow.nfo)
TV database
Manage DirectoriesManage Directories
$curDir['existing_info'][1]$curDir['existing_info'][1]?
#for $cur_indexer_id in $sorted_show_ids: - + #end for diff --git a/gui/slick/js/addExistingShow.js b/gui/slick/js/addExistingShow.js index fa27e10a..1b08f888 100644 --- a/gui/slick/js/addExistingShow.js +++ b/gui/slick/js/addExistingShow.js @@ -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(' 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('' + + ' 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('
  • ') - }); - 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('
  • ' + + '' + + ' ' + + '
  • ') + }); + 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); + }); }); \ No newline at end of file diff --git a/gui/slick/js/configBackupRestore.js b/gui/slick/js/configBackupRestore.js deleted file mode 100644 index 113f5586..00000000 --- a/gui/slick/js/configBackupRestore.js +++ /dev/null @@ -1,24 +0,0 @@ -$(document).ready(function(){ - var loading = ''; - - $('#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); - }); - }); -}); \ No newline at end of file diff --git a/gui/slick/js/configPostProcessing.js b/gui/slick/js/configPostProcessing.js index 036d5c28..312c0b64 100644 --- a/gui/slick/js/configPostProcessing.js +++ b/gui/slick/js/configPostProcessing.js @@ -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' } }); diff --git a/gui/slick/js/displayShow.js b/gui/slick/js/displayShow.js index 2e4daee9..403380f4 100644 --- a/gui/slick/js/displayShow.js +++ b/gui/slick/js/displayShow.js @@ -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 () { diff --git a/gui/slick/js/formwizard.js b/gui/slick/js/formwizard.js index 19534290..bd97d203 100644 --- a/gui/slick/js/formwizard.js +++ b/gui/slick/js/formwizard.js @@ -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') //wrap these elements with fieldset element - $startelement=$theform.find('fieldset.sectionwrap').eq(i).prepend(''+arr[i].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 - 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; i0){ //radio and checkbox elements - var onechecked=false - for (var r=0; r0) - thiswizard.createfieldsets($theform, setting.manualfieldsets) - var $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=$('
    ').insertBefore($sections.eq(0)) //add DIV above the first fieldset.sectionwrap element - $sectionswrapper_inner=$('
    ') //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 = $('
    '), + + //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 = $('
    ').insertBefore($sections.eq(0)); + //create inner DIV of $sectionswrapper that will scroll to reveal a fieldset element + $sectionswrapper_inner = $('
    '); } - 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=$('
    ').data('section', i).html('Step '+(i+1)+'
    '+$section.find('legend:eq(0)').text()+'

    ').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 = $('
    ').data('section', i).html(($stepwords[i] + + ' step') + '
    ' + $section.find('legend:eq(0)').text() + '

    ').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('
     
    ') } - $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=$('
    Prev Step 1 of Next
    ') - $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 = $('
    ' + + 'Prev' + + ' step 1 of ' + + ' Next' + + '
    '); + $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",{"class":"qtip-close "+(a.style.widget?"":S+"-icon"),title:n,"aria-label":n}).prepend(r("",{"class":"ui-icon ui-icon-close",html:"×"})),F.button.appendTo(F.titlebar||H).attr("role","button").click(function(e){return H.hasClass(j)||m.hide(e),s})}function X(){var e=y+"-title";F.titlebar&&z(),F.titlebar=r("
    ",{"class":S+"-titlebar "+(a.style.widget?R("header"):"")}).append(F.title=r("
    ",{id:e,"class":S+"-title","aria-atomic":i})).insertBefore(F.content).delegate(".qtip-close","mousedown keydown mouseup keyup mouseout",function(e){r(this).toggleClass("ui-state-active ui-state-focus",e.type.substr(-4)==="down")}).delegate(".qtip-close","mouseover mouseout",function(e){r(this).toggleClass("ui-state-hover",e.type==="mouseover")}),a.content.title.button&&W()}function V(e){var t=F.button;if(!m.rendered)return s;e?W():t.remove()}function J(e,t){var n=F.title;if(!m.rendered||!e)return s;r.isFunction(e)&&(e=e.call(u,I.event,m));if(e===s||!e&&e!=="")return z(s);e.jquery&&e.length>0?n.empty().append(e.css({display:"block"})):n.html(e),t!==s&&m.rendered&&H[0].offsetWidth>0&&m.reposition(I.event)}function K(e){e&&r.isFunction(e.done)&&e.done(function(e){Q(e,null,s)})}function Q(e,t,i){function f(e){function a(n){n&&(delete u[n.src],clearTimeout(m.timers.img[n.src]),r(n).unbind(B)),r.isEmptyObject(u)&&(t!==s&&m.reposition(I.event),e())}var i,u={};if((i=o.find("img[src]:not([height]):not([width])")).length===0)return a();i.each(function(e,t){if(u[t.src]!==n)return;var i=0,s=3;(function o(){if(t.height||t.width||i>s)return a(t);i+=1,m.timers.img[t.src]=setTimeout(o,700)})(),r(t).bind("error"+B+" load"+B,function(){a(this)}),u[t.src]=t})}var o=F.content;return!m.rendered||!e?s:(r.isFunction(e)&&(e=e.call(u,I.event,m)||""),i!==s&&K(a.content.deferred),e.jquery&&e.length>0?o.empty().append(e.css({display:"block"})):o.html(e),m.rendered<0?H.queue("fx",f):(P=0,f(r.noop)),m)}function G(){function h(e){if(H.hasClass(j))return s;clearTimeout(m.timers.show),clearTimeout(m.timers.hide);var t=function(){m.toggle(i,e)};a.show.delay>0?m.timers.show=setTimeout(t,a.show.delay):t()}function p(e){if(H.hasClass(j)||A||P)return s;var t=r(e.relatedTarget||e.target),i=t.closest(N)[0]===H[0],u=t[0]===o.show[0];clearTimeout(m.timers.show),clearTimeout(m.timers.hide);if(n.target==="mouse"&&i||a.hide.fixed&&/mouse(out|leave|move)/.test(e.type)&&(i||u)){try{e.preventDefault(),e.stopImmediatePropagation()}catch(f){}return}a.hide.delay>0?m.timers.hide=setTimeout(function(){m.hide(e)},a.hide.delay):m.hide(e)}function d(e){if(H.hasClass(j))return s;clearTimeout(m.timers.inactive),m.timers.inactive=setTimeout(function(){m.hide(e)},a.hide.inactive)}function v(e){m.rendered&&H[0].offsetWidth>0&&m.reposition(e)}var n=a.position,o={show:a.show.target,hide:a.hide.target,viewport:r(n.viewport),document:r(t),body:r(t.body),window:r(e)},l={show:r.trim(""+a.show.event).split(" "),hide:r.trim(""+a.hide.event).split(" ")},c=r.browser.msie&&parseInt(r.browser.version,10)===6;H.bind("mouseenter"+B+" mouseleave"+B,function(e){var t=e.type==="mouseenter";t&&m.focus(e),H.toggleClass(L,t)}),/mouse(out|leave)/i.test(a.hide.event)&&a.hide.leave==="window"&&o.window.bind("mouseout"+B+" blur"+B,function(e){!/select|option/.test(e.target.nodeName)&&!e.relatedTarget&&m.hide(e)}),a.hide.fixed?(o.hide=o.hide.add(H),H.bind("mouseover"+B,function(){H.hasClass(j)||clearTimeout(m.timers.hide)})):/mouse(over|enter)/i.test(a.show.event)&&o.hide.bind("mouseleave"+B,function(e){clearTimeout(m.timers.show)}),(""+a.hide.event).indexOf("unfocus")>-1&&n.container.closest("html").bind("mousedown"+B+" touchstart"+B,function(e){var t=r(e.target),n=m.rendered&&!H.hasClass(j)&&H[0].offsetWidth>0,i=t.parents(N).filter(H[0]).length>0;t[0]!==u[0]&&t[0]!==H[0]&&!i&&!u.has(t[0]).length&&!t.attr("disabled")&&m.hide(e)}),"number"==typeof a.hide.inactive&&(o.show.bind("qtip-"+f+"-inactive",d),r.each(b.inactiveEvents,function(e,t){o.hide.add(F.tooltip).bind(t+B+"-inactive",d)})),r.each(l.hide,function(e,t){var n=r.inArray(t,l.show),i=r(o.hide);n>-1&&i.add(o.show).length===i.length||t==="unfocus"?(o.show.bind(t+B,function(e){H[0].offsetWidth>0?p(e):h(e)}),delete l.show[n]):o.hide.bind(t+B,p)}),r.each(l.show,function(e,t){o.show.bind(t+B,h)}),"number"==typeof a.hide.distance&&o.show.add(H).bind("mousemove"+B,function(e){var t=I.origin||{},n=a.hide.distance,r=Math.abs;(r(e.pageX-t.pageX)>=n||r(e.pageY-t.pageY)>=n)&&m.hide(e)}),n.target==="mouse"&&(o.show.bind("mousemove"+B,_),n.adjust.mouse&&(a.hide.event&&(H.bind("mouseleave"+B,function(e){(e.relatedTarget||e.target)!==o.show[0]&&m.hide(e)}),F.target.bind("mouseenter"+B+" mouseleave"+B,function(e){I.onTarget=e.type==="mouseenter"})),o.document.bind("mousemove"+B,function(e){m.rendered&&I.onTarget&&!H.hasClass(j)&&H[0].offsetWidth>0&&m.reposition(e||E)}))),(n.adjust.resize||o.viewport.length)&&(r.event.special.resize?o.viewport:o.window).bind("resize"+B,v),o.window.bind("scroll"+B,v)}function Y(){var n=[a.show.target[0],a.hide.target[0],m.rendered&&F.tooltip[0],a.position.container[0],a.position.viewport[0],a.position.container.closest("html")[0],e,t];m.rendered?r([]).pushStack(r.grep(n,function(e){return typeof e=="object"})).unbind(B):a.show.target.unbind(B+"-create")}var m=this,g=t.body,y=S+"-"+f,A=0,P=0,H=r(),B=".qtip-"+f,j="qtip-disabled",F,I;m.id=f,m.rendered=s,m.destroyed=s,m.elements=F={target:u},m.timers={img:{}},m.options=a,m.checks={},m.plugins={},m.cache=I={event:{},target:r(),disabled:s,attr:l,onTarget:s,lastClass:""},m.checks.builtin={"^id$":function(e,t,n){var o=n===i?b.nextid:n,u=S+"-"+o;o!==s&&o.length>0&&!r("#"+u).length&&(H[0].id=u,F.content[0].id=u+"-content",F.title[0].id=u+"-title")},"^content.text$":function(e,t,n){Q(a.content.text)},"^content.deferred$":function(e,t,n){K(a.content.deferred)},"^content.title.text$":function(e,t,n){if(!n)return z();!F.title&&n&&X(),J(n)},"^content.title.button$":function(e,t,n){V(n)},"^position.(my|at)$":function(e,t,n){"string"==typeof n&&(e[t]=new w.Corner(n))},"^position.container$":function(e,t,n){m.rendered&&H.appendTo(n)},"^show.ready$":function(){m.rendered?m.toggle(i):m.render(1)},"^style.classes$":function(e,t,n){H.attr("class",S+" qtip "+n)},"^style.width|height":function(e,t,n){H.css(t,n)},"^style.widget|content.title":U,"^events.(render|show|move|hide|focus|blur)$":function(e,t,n){H[(r.isFunction(n)?"":"un")+"bind"]("tooltip"+t,n)},"^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)":function(){var e=a.position;H.attr("tracking",e.target==="mouse"&&e.adjust.mouse),Y(),G()}},r.extend(m,{_triggerEvent:function(e,t,n){var i=r.Event("tooltip"+e);return i.originalEvent=(n?r.extend({},n):o)||I.event||o,H.trigger(i,[m].concat(t||[])),!i.isDefaultPrevented()},render:function(e){if(m.rendered)return m;var t=a.content.text,n=a.content.title,o=a.position;return r.attr(u[0],"aria-describedby",y),H=F.tooltip=r("
    ",{id:y,"class":[S,C,a.style.classes,S+"-pos-"+a.position.my.abbrev()].join(" "),width:a.style.width||"",height:a.style.height||"",tracking:o.target==="mouse"&&o.adjust.mouse,role:"alert","aria-live":"polite","aria-atomic":s,"aria-describedby":y+"-content","aria-hidden":i}).toggleClass(j,I.disabled).data("qtip",m).appendTo(a.position.container).append(F.content=r("
    ",{"class":S+"-content",id:y+"-content","aria-atomic":i})),m.rendered=-1,A=1,n.text?(X(),r.isFunction(n.text)||J(n.text,s)):n.button&&W(),(!r.isFunction(t)||t.then)&&Q(t,s),m.rendered=i,U(),r.each(a.events,function(e,t){r.isFunction(t)&&H.bind(e==="toggle"?"tooltipshow tooltiphide":"tooltip"+e,t)}),r.each(w,function(){this.initialize==="render"&&this(m)}),G(),H.queue("fx",function(t){m._triggerEvent("render"),A=0,(a.show.ready||e)&&m.toggle(i,I.event,s),t()}),m},get:function(e){var t,n;switch(e.toLowerCase()){case"dimensions":t={height:H.outerHeight(s),width:H.outerWidth(s)};break;case"offset":t=w.offset(H,a.position.container);break;default:n=q(e.toLowerCase()),t=n[0][n[1]],t=t.precedance?t.string():t}return t},set:function(e,t){function h(e,t){var n,r,i;for(n in l)for(r in l[n])if(i=(new RegExp(r,"i")).exec(e))t.push(i),l[n][r].apply(m,t)}var n=/^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,u=/^content\.(title|attr)|style/i,f=s,l=m.checks,c;return"string"==typeof e?(c=e,e={},e[c]=t):e=r.extend(i,{},e),r.each(e,function(t,i){var s=q(t.toLowerCase()),o;o=s[0][s[1]],s[0][s[1]]="object"==typeof i&&i.nodeType?r(i):i,e[t]=[s[0],s[1],i,o],f=n.test(t)||f}),D(a),A=1,r.each(e,h),A=0,m.rendered&&H[0].offsetWidth>0&&f&&m.reposition(a.position.target==="mouse"?o:I.event),m},toggle:function(e,n){function b(){e?(r.browser.msie&&H[0].style.removeAttribute("filter"),H.css("overflow",""),"string"==typeof u.autofocus&&r(u.autofocus,H).focus(),u.target.trigger("qtip-"+f+"-inactive")):H.css({display:"",visibility:"",opacity:"",left:"",top:""}),m._triggerEvent(e?"visible":"hidden")}if(n){if(/over|enter/.test(n.type)&&/out|leave/.test(I.event.type)&&a.show.target.add(n.target).length===a.show.target.length&&H.has(n.relatedTarget).length)return m;I.event=r.extend({},n)}if(!m.rendered)return e?m.render(1):m;var o=e?"show":"hide",u=a[o],l=a[e?"hide":"show"],c=a.position,h=a.content,p=H[0].offsetWidth>0,d=e||u.target.length===1,v=!n||u.target.length<2||I.target[0]===n.target,g,y;return(typeof e).search("boolean|number")&&(e=!p),!H.is(":animated")&&p===e&&v?m:m._triggerEvent(o,[90])?(r.attr(H[0],"aria-hidden",!e),e?(I.origin=r.extend({},E),m.focus(n),r.isFunction(h.text)&&Q(h.text,s),r.isFunction(h.title.text)&&J(h.title.text,s),!M&&c.target==="mouse"&&c.adjust.mouse&&(r(t).bind("mousemove.qtip",_),M=i),m.reposition(n,arguments[2]),!u.solo||r(N,u.solo).not(H).qtip("hide",r.Event("tooltipsolo"))):(clearTimeout(m.timers.show),delete I.origin,M&&!r(N+'[tracking="true"]:visible',u.solo).not(H).length&&(r(t).unbind("mousemove.qtip"),M=s),m.blur(n)),u.effect===s||d===s?(H[o](),b.call(H)):r.isFunction(u.effect)?(H.stop(1,1),u.effect.call(H,m),H.queue("fx",function(e){b(),e()})):H.fadeTo(90,e?1:0,b),e&&u.target.trigger("qtip-"+f+"-inactive"),m):m},show:function(e){return m.toggle(i,e)},hide:function(e){return m.toggle(s,e)},focus:function(e){if(!m.rendered)return m;var t=r(N),n=parseInt(H[0].style.zIndex,10),i=b.zindex+t.length,s=r.extend({},e),o;return H.hasClass(k)||m._triggerEvent("focus",[i],s)&&(n!==i&&(t.each(function(){this.style.zIndex>n&&(this.style.zIndex=this.style.zIndex-1)}),t.filter("."+k).qtip("blur",s)),H.addClass(k)[0].style.zIndex=i),m},blur:function(e){return H.removeClass(k),m._triggerEvent("blur",[H.css("zIndex")],e),m},reposition:function(n,i){if(!m.rendered||A)return m;A=1;var o=a.position.target,u=a.position,f=u.my,l=u.at,g=u.adjust,y=g.method.split(" "),b=H.outerWidth(s),S=H.outerHeight(s),x=0,T=0,N=H.css("position"),C=u.viewport,k={left:0,top:0},L=u.container,O=H[0].offsetWidth>0,M=n&&n.type==="scroll",_=r(e),D,P;if(r.isArray(o)&&o.length===2)l={x:h,y:c},k={left:o[0],top:o[1]};else if(o==="mouse"&&(n&&n.pageX||I.event.pageX))l={x:h,y:c},n=E&&E.pageX&&(g.mouse||!n||!n.pageX)?{pageX:E.pageX,pageY:E.pageY}:(!n||n.type!=="resize"&&n.type!=="scroll"?n&&n.pageX&&n.type==="mousemove"?n:!g.mouse&&I.origin&&I.origin.pageX&&a.show.distance?I.origin:n:I.event)||n||I.event||E||{},N!=="static"&&(k=L.offset()),k={left:n.pageX-k.left,top:n.pageY-k.top},g.mouse&&M&&(k.left-=E.scrollX-_.scrollLeft(),k.top-=E.scrollY-_.scrollTop());else{o==="event"&&n&&n.target&&n.type!=="scroll"&&n.type!=="resize"?I.target=r(n.target):o!=="event"&&(I.target=r(o.jquery?o:F.target)),o=I.target,o=r(o).eq(0);if(o.length===0)return m;o[0]===t||o[0]===e?(x=w.iOS?e.innerWidth:o.width(),T=w.iOS?e.innerHeight:o.height(),o[0]===e&&(k={top:(C||o).scrollTop(),left:(C||o).scrollLeft()})):w.imagemap&&o.is("area")?D=w.imagemap(m,o,l,w.viewport?y:s):w.svg&&o[0].ownerSVGElement?D=w.svg(m,o,l,w.viewport?y:s):(x=o.outerWidth(s),T=o.outerHeight(s),k=w.offset(o,L)),D&&(x=D.width,T=D.height,P=D.offset,k=D.position);if(w.iOS>3.1&&w.iOS<4.1||w.iOS>=4.3&&w.iOS<4.33||!w.iOS&&N==="fixed")k.left-=_.scrollLeft(),k.top-=_.scrollTop();k.left+=l.x===d?x:l.x===v?x/2:0,k.top+=l.y===p?T:l.y===v?T/2:0}return k.left+=g.x+(f.x===d?-b:f.x===v?-b/2:0),k.top+=g.y+(f.y===p?-S:f.y===v?-S/2:0),w.viewport?(k.adjusted=w.viewport(m,k,u,x,T,b,S),P&&k.adjusted.left&&(k.left+=P.left),P&&k.adjusted.top&&(k.top+=P.top)):k.adjusted={left:0,top:0},m._triggerEvent("move",[k,C.elem||C],n)?(delete k.adjusted,i===s||!O||isNaN(k.left)||isNaN(k.top)||o==="mouse"||!r.isFunction(u.effect)?H.css(k):r.isFunction(u.effect)&&(u.effect.call(H,m,r.extend({},k)),H.queue(function(e){r(this).css({opacity:"",height:""}),r.browser.msie&&this.style.removeAttribute("filter"),e()})),A=0,m):m},disable:function(e){return"boolean"!=typeof e&&(e=!H.hasClass(j)&&!I.disabled),m.rendered?(H.toggleClass(j,e),r.attr(H[0],"aria-disabled",e)):I.disabled=!!e,m},enable:function(){return m.disable(s)},destroy:function(){var e=u[0],t=r.attr(e,O),n=u.data("qtip");m.destroyed=i,m.rendered&&(H.stop(1,0).remove(),r.each(m.plugins,function(){this.destroy&&this.destroy()})),clearTimeout(m.timers.show),clearTimeout(m.timers.hide),Y();if(!n||m===n)r.removeData(e,"qtip"),a.suppress&&t&&(r.attr(e,"title",t),u.removeAttr(O)),u.removeAttr("aria-describedby");return u.unbind(".qtip-"+f),delete x[m.id],u}})}function H(e,n){var u,a,f,l,c,h=r(this),p=r(t.body),d=this===t?p:h,v=h.metadata?h.metadata(n.metadata):o,m=n.metadata.type==="html5"&&v?v[n.metadata.name]:o,g=h.data(n.metadata.name||"qtipopts");try{g=typeof g=="string"?r.parseJSON(g):g}catch(y){}l=r.extend(i,{},b.defaults,n,typeof g=="object"?D(g):o,D(m||v)),a=l.position,l.id=e;if("boolean"==typeof l.content.text){f=h.attr(l.content.attr);if(l.content.attr===s||!f)return s;l.content.text=f}a.container.length||(a.container=p),a.target===s&&(a.target=d),l.show.target===s&&(l.show.target=d),l.show.solo===i&&(l.show.solo=a.container.closest("body")),l.hide.target===s&&(l.hide.target=d),l.position.viewport===i&&(l.position.viewport=a.container),a.container=a.container.eq(0),a.at=new w.Corner(a.at),a.my=new w.Corner(a.my);if(r.data(this,"qtip"))if(l.overwrite)h.qtip("destroy");else if(l.overwrite===s)return s;return l.suppress&&(c=r.attr(this,"title"))&&r(this).removeAttr("title").attr(O,c).attr("title",""),u=new P(h,l,e,!!f),r.data(this,"qtip",u),h.bind("remove.qtip-"+e+" removeqtip.qtip-"+e,function(){u.destroy()}),u}function B(e){var t=this,n=e.elements.tooltip,o=e.options.content.ajax,u=b.defaults.content.ajax,a=".qtip-ajax",f=/)<[^<]*)*<\/script>/gi,l=i,c=s,h;e.checks.ajax={"^content.ajax":function(e,r,i){r==="ajax"&&(o=i),r==="once"?t.init():o&&o.url?t.load():n.unbind(a)}},r.extend(t,{init:function(){return o&&o.url&&n.unbind(a)[o.once?"one":"bind"]("tooltipshow"+a,t.load),t},load:function(n){function g(){var t;if(e.destroyed)return;l=s,v&&(c=i,e.show(n.originalEvent)),(t=u.complete||o.complete)&&r.isFunction(t)&&t.apply(o.context||e,arguments)}function y(t,n,i){var s;if(e.destroyed)return;d&&"string"==typeof t&&(t=r("
    ").append(t.replace(f,"")).find(d)),(s=u.success||o.success)&&r.isFunction(s)?s.call(o.context||e,t,n,i):e.set("content.text",t)}function b(t,n,r){if(e.destroyed||t.status===0)return;e.set("content.text",n+": "+r)}if(c){c=s;return}var a=o.url.lastIndexOf(" "),p=o.url,d,v=!o.loading&&l;if(v)try{n.preventDefault()}catch(m){}else if(n&&n.isDefaultPrevented())return t;h&&h.abort&&h.abort(),a>-1&&(d=p.substr(a),p=p.substr(0,a)),h=r.ajax(r.extend({error:u.error||b,context:e},o,{url:p,success:y,complete:g}))},destroy:function(){h&&h.abort&&h.abort(),e.destroyed=i}}),t.init()}function j(e,t,n){var r=Math.ceil(t/2),i=Math.ceil(n/2),s={bottomright:[[0,0],[t,n],[t,0]],bottomleft:[[0,0],[t,0],[0,n]],topright:[[0,n],[t,0],[t,n]],topleft:[[0,0],[0,n],[t,n]],topcenter:[[0,n],[r,0],[t,n]],bottomcenter:[[0,0],[t,0],[r,n]],rightcenter:[[0,0],[t,i],[0,n]],leftcenter:[[t,0],[t,n],[0,i]]};return s.lefttop=s.bottomright,s.righttop=s.bottomleft,s.leftbottom=s.topright,s.rightbottom=s.topleft,s[e.string()]}function F(e,t){function A(e){var t=E.is(":visible");E.show(),e(),E.toggle(t)}function O(){x.width=g.height,x.height=g.width}function M(){x.width=g.width,x.height=g.height}function _(t,r,o,f){if(!b.tip)return;var l=m.corner.clone(),w=o.adjusted,E=e.options.position.adjust.method.split(" "),x=E[0],T=E[1]||E[0],N={left:s,top:s,x:0,y:0},C,k={},L;m.corner.fixed!==i&&(x===y&&l.precedance===u&&w.left&&l.y!==v?l.precedance=l.precedance===u?a:u:x!==y&&w.left&&(l.x=l.x===v?w.left>0?h:d:l.x===h?d:h),T===y&&l.precedance===a&&w.top&&l.x!==v?l.precedance=l.precedance===a?u:a:T!==y&&w.top&&(l.y=l.y===v?w.top>0?c:p:l.y===c?p:c),l.string()!==S.corner.string()&&(S.top!==w.top||S.left!==w.left)&&m.update(l,s)),C=m.position(l,w),C[l.x]+=P(l,l.x),C[l.y]+=P(l,l.y),C.right!==n&&(C.left=-C.right),C.bottom!==n&&(C.top=-C.bottom),C.user=Math.max(0,g.offset);if(N.left=x===y&&!!w.left)l.x===v?k["margin-left"]=N.x=C["margin-left"]:(L=C.right!==n?[w.left,-C.left]:[-w.left,C.left],(N.x=Math.max(L[0],L[1]))>L[0]&&(o.left-=w.left,N.left=s),k[C.right!==n?d:h]=N.x);if(N.top=T===y&&!!w.top)l.y===v?k["margin-top"]=N.y=C["margin-top"]:(L=C.bottom!==n?[w.top,-C.top]:[-w.top,C.top],(N.y=Math.max(L[0],L[1]))>L[0]&&(o.top-=w.top,N.top=s),k[C.bottom!==n?p:c]=N.y);b.tip.css(k).toggle(!(N.x&&N.y||l.x===v&&N.y||l.y===v&&N.x)),o.left-=C.left.charAt?C.user:x!==y||N.top||!N.left&&!N.top?C.left:0,o.top-=C.top.charAt?C.user:T!==y||N.left||!N.left&&!N.top?C.top:0,S.left=w.left,S.top=w.top,S.corner=l.clone()}function D(){var t=g.corner,n=e.options.position,r=n.at,o=n.my.string?n.my.string():n.my;return t===s||o===s&&r===s?s:(t===i?m.corner=new w.Corner(o):t.string||(m.corner=new w.Corner(t),m.corner.fixed=i),S.corner=new w.Corner(m.corner.string()),m.corner.string()!=="centercenter")}function P(e,t,n){t=t?t:e[e.precedance];var r=b.titlebar&&e.y===c,i=r?b.titlebar:E,s="border-"+t+"-width",o=function(e){return parseInt(e.css(s),10)},u;return A(function(){u=(n?o(n):o(b.content)||o(i)||o(E))||0}),u}function H(e){var t=b.titlebar&&e.y===c,n=t?b.titlebar:b.content,i=r.browser.mozilla,s=i?"-moz-":r.browser.webkit?"-webkit-":"",o="border-radius-"+e.y+e.x,u="border-"+e.y+"-"+e.x+"-radius",a=function(e){return parseInt(n.css(e),10)||parseInt(E.css(e),10)},f;return A(function(){f=a(u)||a(s+u)||a(s+o)||a(o)||0}),f}function B(e){function N(e,t,n){var r=e.css(t)||p;return n&&r===e.css(n)?s:f.test(r)?s:r}var t,n,o,u=b.tip.css("cssText",""),a=e||m.corner,f=/rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i,l="border-"+a[a.precedance]+"-color",h="background-color",p="transparent",d=" !important",y=b.titlebar,w=y&&(a.y===c||a.y===v&&u.position().top+x.height/2+g.offset-1,s=n*(i?.5:1),o=Math.pow,u=Math.round,c,h,p,d=Math.sqrt(o(s,2)+o(r,2)),m=[N/s*d,N/r*d];return m[2]=Math.sqrt(o(m[0],2)-o(N,2)),m[3]=Math.sqrt(o(m[1],2)-o(N,2)),c=d+m[2]+m[3]+(i?0:m[0]),h=c/d,p=[u(h*r),u(h*n)],{height:p[t?0:1],width:p[t?1:0]}}function I(e,t,n){return"'}var m=this,g=e.options.style.tip,b=e.elements,E=b.tooltip,S={top:0,left:0},x={width:g.width,height:g.height},T={},N=g.border||0,C=".qtip-tip",k=!!(r("")[0]||{}).getContext,L;m.corner=o,m.mimic=o,m.border=N,m.offset=g.offset,m.size=x,e.checks.tip={"^position.my|style.tip.(corner|mimic|border)$":function(){m.init()||m.destroy(),e.reposition()},"^style.tip.(height|width)$":function(){x={width:g.width,height:g.height},m.create(),m.update(),e.reposition()},"^content.title.text|style.(classes|widget)$":function(){b.tip&&b.tip.length&&m.update()}},r.extend(m,{init:function(){var e=D()&&(k||r.browser.msie);return e&&(m.create(),m.update(),E.unbind(C).bind("tooltipmove"+C,_)),e},create:function(){var e=x.width,t=x.height,n;b.tip&&b.tip.remove(),b.tip=r("
    ",{"class":"qtip-tip"}).css({width:e,height:t}).prependTo(E),k?r("").appendTo(b.tip)[0].getContext("2d").save():(n=I("shape",'coordorigin="0,0"',"position:absolute;"),b.tip.html(n+n),r("*",b.tip).bind("click mousedown",function(e){e.stopPropagation()}))},update:function(e,t){var n=b.tip,f=n.children(),l=x.width,y=x.height,C=g.mimic,L=Math.round,A,_,D,H,q;e||(e=S.corner||m.corner),C===s?C=e:(C=new w.Corner(C),C.precedance=e.precedance,C.x==="inherit"?C.x=e.x:C.y==="inherit"?C.y=e.y:C.x===C.y&&(C[e.precedance]=e[e.precedance])),A=C.precedance,e.precedance===u?O():M(),b.tip.css({width:l=x.width,height:y=x.height}),B(e),T.border!=="transparent"?(N=P(e,o),g.border===0&&N>0&&(T.fill=T.border),m.border=N=g.border!==i?g.border:N):m.border=N=0,D=j(C,l,y),m.size=q=F(e),n.css(q).css("line-height",q.height+"px"),e.precedance===a?H=[L(C.x===h?N:C.x===d?q.width-l-N:(q.width-l)/2),L(C.y===c?q.height-y:0)]:H=[L(C.x===h?q.width-l:0),L(C.y===c?N:C.y===p?q.height-y-N:(q.height-y)/2)],k?(f.attr(q),_=f[0].getContext("2d"),_.restore(),_.save(),_.clearRect(0,0,3e3,3e3),_.fillStyle=T.fill,_.strokeStyle=T.border,_.lineWidth=N*2,_.lineJoin="miter",_.miterLimit=100,_.translate(H[0],H[1]),_.beginPath(),_.moveTo(D[0][0],D[0][1]),_.lineTo(D[1][0],D[1][1]),_.lineTo(D[2][0],D[2][1]),_.closePath(),N&&(E.css("background-clip")==="border-box"&&(_.strokeStyle=T.fill,_.stroke()),_.strokeStyle=T.border,_.stroke()),_.fill()):(D="m"+D[0][0]+","+D[0][1]+" l"+D[1][0]+","+D[1][1]+" "+D[2][0]+","+D[2][1]+" xe",H[2]=N&&/^(r|b)/i.test(e.string())?parseFloat(r.browser.version,10)===8?2:1:0,f.css({coordsize:l+N+" "+(y+N),antialias:""+(C.string().indexOf(v)>-1),left:H[0],top:H[1],width:l+N,height:y+N}).each(function(e){var t=r(this);t[t.prop?"prop":"attr"]({coordsize:l+N+" "+(y+N),path:D,fillcolor:T.fill,filled:!!e,stroked:!e}).toggle(!!N||!!e),!e&&t.html()===""&&t.html(I("stroke",'weight="'+N*2+'px" color="'+T.border+'" miterlimit="1000" joinstyle="miter"'))})),t!==s&&m.position(e)},position:function(e){var t=b.tip,n={},i=Math.max(0,g.offset),o,p,d;return g.corner===s||!t?s:(e=e||m.corner,o=e.precedance,p=F(e),d=[e.x,e.y],o===u&&d.reverse(),r.each(d,function(t,r){var s,u,d;r===v?(s=o===a?h:c,n[s]="50%",n["margin-"+s]=-Math.round(p[o===a?f:l]/2)+i):(s=P(e,r),u=P(e,r,b.content),d=H(e),n[r]=t?u:i+(d>s?d:-s))}),n[e[o]]-=p[o===u?f:l],t.css({top:"",bottom:"",left:"",right:"",margin:""}).css(n),n)},destroy:function(){b.tip&&b.tip.remove(),b.tip=!1,E.unbind(C)}}),m.init()}function I(n){function y(){m=r(v,f).not("[disabled]").map(function(){return typeof this.focus=="function"?this:null})}function b(e){m.length<1&&e.length?e.not("body").blur():m.first().focus()}function E(e){var t=r(e.target),n=t.closest(".qtip"),i;i=n.length<1?s:parseInt(n[0].style.zIndex,10)>parseInt(f[0].style.zIndex,10),!i&&r(e.target).closest(N)[0]!==f[0]&&b(t)}var o=this,u=n.options.show.modal,a=n.elements,f=a.tooltip,l="#qtip-overlay",c=".qtipmodal",h=c+n.id,p="is-modal-qtip",d=r(t.body),v=w.modal.focusable.join(","),m={},g;n.checks.modal={"^show.modal.(on|blur)$":function(){o.init(),a.overlay.toggle(f.is(":visible"))},"^content.text$":function(){y()}},r.extend(o,{init:function(){return u.on?(g=o.create(),f.attr(p,i).css("z-index",w.modal.zindex+r(N+"["+p+"]").length).unbind(c).unbind(h).bind("tooltipshow"+c+" tooltiphide"+c,function(e,t,n){var i=e.originalEvent;if(e.target===f[0])if(i&&e.type==="tooltiphide"&&/mouse(leave|enter)/.test(i.type)&&r(i.relatedTarget).closest(g[0]).length)try{e.preventDefault()}catch(s){}else(!i||i&&!i.solo)&&o[e.type.replace("tooltip","")](e,n)}).bind("tooltipfocus"+c,function(e){if(e.isDefaultPrevented()||e.target!==f[0])return;var t=r(N).filter("["+p+"]"),n=w.modal.zindex+t.length,i=parseInt(f[0].style.zIndex,10);g[0].style.zIndex=n-2,t.each(function(){this.style.zIndex>i&&(this.style.zIndex-=1)}),t.end().filter("."+k).qtip("blur",e.originalEvent),f.addClass(k)[0].style.zIndex=n;try{e.preventDefault()}catch(s){}}).bind("tooltiphide"+c,function(e){e.target===f[0]&&r("["+p+"]").filter(":visible").not(f).last().qtip("focus",e)}),u.escape&&r(t).unbind(h).bind("keydown"+h,function(e){e.keyCode===27&&f.hasClass(k)&&n.hide(e)}),u.blur&&a.overlay.unbind(h).bind("click"+h,function(e){f.hasClass(k)&&n.hide(e)}),y(),o):o},create:function(){function i(){g.css({height:n.height(),width:n.width()})}var t=r(l),n=r(e);return t.length?a.overlay=t.insertAfter(r(N).last()):(g=a.overlay=r("
    ",{id:l.substr(1),html:"
    ",mousedown:function(){return s}}).hide().insertAfter(r(N).last()),n.unbind(c).bind("resize"+c,i),i(),g)},toggle:function(e,t,n){if(e&&e.isDefaultPrevented())return o;var a=u.effect,l=t?"show":"hide",c=g.is(":visible"),v=r("["+p+"]").filter(":visible").not(f),m;return g||(g=o.create()),g.is(":animated")&&c===t&&g.data("toggleState")!==s||!t&&v.length?o:(t?(g.css({left:0,top:0}),g.toggleClass("blurs",u.blur),u.stealfocus!==s&&(d.bind("focusin"+h,E),b(r("body :focus")))):d.unbind("focusin"+h),g.stop(i,s).data("toggleState",t),r.isFunction(a)?a.call(g,t):a===s?g[l]():g.fadeTo(parseInt(n,10)||90,t?1:0,function(){t||r(this).hide()}),t||g.queue(function(e){g.css({left:"",top:""}).removeData("toggleState"),e()}),o)},show:function(e,t){return o.toggle(e,i,t)},hide:function(e,t){return o.toggle(e,s,t)},destroy:function(){var e=g;return e&&(e=r("["+p+"]").not(f).length<1,e?(a.overlay.remove(),r(t).unbind(c)):a.overlay.unbind(c+n.id),d.unbind("focusin"+h)),f.removeAttr(p).unbind(c)}}),o.init()}function q(n){var o=this,u=n.elements,a=n.options,c=u.tooltip,h=".ie6-"+n.id,p=r("select, object").length<1,d=0,v=s,m;n.checks.ie6={"^content|style$":function(e,t,n){redraw()}},r.extend(o,{init:function(){var n=r(e),s;p&&(u.bgiframe=r(''),u.bgiframe.appendTo(c),c.bind("tooltipmove"+h,o.adjustBGIFrame)),m=r("
    ",{id:"qtip-rcontainer"}).appendTo(t.body),o.redraw(),u.overlay&&!v&&(s=function(){u.overlay[0].style.top=n.scrollTop()+"px"},n.bind("scroll.qtip-ie6, resize.qtip-ie6",s),s(),u.overlay.addClass("qtipmodal-ie6fix"),v=i)},adjustBGIFrame:function(){var e=n.get("dimensions"),t=n.plugins.tip,r=u.tip,i,s;s=parseInt(c.css("border-left-width"),10)||0,s={left:-s,top:-s},t&&r&&(i=t.corner.precedance==="x"?["width","left"]:["height","top"],s[i[1]]-=r[i[0]]()),u.bgiframe.css(s).css(e)},redraw:function(){if(n.rendered<1||d)return o;var e=a.style,t=a.position.container,r,i,s,u;return d=1,e.height&&c.css(l,e.height),e.width?c.css(f,e.width):(c.css(f,"").appendTo(m),i=c.width(),i%2<1&&(i+=1),s=c.css("max-width")||"",u=c.css("min-width")||"",r=(s+u).indexOf("%")>-1?t.width()/100:0,s=(s.indexOf("%")>-1?r:1)*parseInt(s,10)||i,u=(u.indexOf("%")>-1?r:1)*parseInt(u,10)||0,i=s+u?Math.min(Math.max(i,u),s):i,c.css(f,Math.round(i)).appendTo(t)),d=0,o},destroy:function(){p&&u.bgiframe.remove(),c.unbind(h)}}),o.init()}var i=!0,s=!1,o=null,u="x",a="y",f="width",l="height",c="top",h="left",p="bottom",d="right",v="center",m="flip",g="flipinvert",y="shift",b,w,E,S="qtip",x={},T=["ui-widget","ui-tooltip"],N="div.qtip."+S,C=S+"-default",k=S+"-focus",L=S+"-hover",A="_replacedByqTip",O="oldtitle",M;b=r.fn.qtip=function(e,t,u){var a=(""+e).toLowerCase(),f=o,l=r.makeArray(arguments).slice(1),c=l[l.length-1],h=this[0]?r.data(this[0],"qtip"):o;if(!arguments.length&&h||a==="api")return h;if("string"==typeof e)return this.each(function(){var e=r.data(this,"qtip");if(!e)return i;c&&c.timeStamp&&(e.cache.event=c);if(a!=="option"&&a!=="options"||!t)e[a]&&e[a].apply(e[a],l);else{if(!r.isPlainObject(t)&&u===n)return f=e.get(t),s;e.set(t,u)}}),f!==o?f:this;if("object"==typeof e||!arguments.length)return h=D(r.extend(i,{},e)),b.bind.call(this,h,c)},b.bind=function(e,t){return this.each(function(o){function p(e){function t(){c.render(typeof e=="object"||u.show.ready),a.show.add(a.hide).unbind(l)}if(c.cache.disabled)return s;c.cache.event=r.extend({},e),c.cache.target=e?r(e.target):[n],u.show.delay>0?(clearTimeout(c.timers.show),c.timers.show=setTimeout(t,u.show.delay),f.show!==f.hide&&a.hide.bind(f.hide,function(){clearTimeout(c.timers.show)})):t()}var u,a,f,l,c,h;h=r.isArray(e.id)?e.id[o]:e.id,h=!h||h===s||h.length<1||x[h]?b.nextid++:x[h]=h,l=".qtip-"+h+"-create",c=H.call(this,h,e);if(c===s)return i;u=c.options,r.each(w,function(){this.initialize==="initialize"&&this(c)}),a={show:u.show.target,hide:u.hide.target},f={show:r.trim(""+u.show.event).replace(/ /g,l+" ")+l,hide:r.trim(""+u.hide.event).replace(/ /g,l+" ")+l},/mouse(over|enter)/i.test(f.show)&&!/mouse(out|leave)/i.test(f.hide)&&(f.hide+=" mouseleave"+l),a.show.bind("mousemove"+l,function(e){_(e),c.cache.onTarget=i}),a.show.bind(f.show,p),(u.show.ready||u.prerender)&&p(t)}).attr("data-hasqtip",i)},w=b.plugins={Corner:function(e){e=(""+e).replace(/([A-Z])/," $1").replace(/middle/gi,v).toLowerCase(),this.x=(e.match(/left|right/i)||e.match(/center/)||["inherit"])[0].toLowerCase(),this.y=(e.match(/top|bottom|center/i)||["inherit"])[0].toLowerCase();var t=e.charAt(0);this.precedance=t==="t"||t==="b"?a:u,this.string=function(){return this.precedance===a?this.y+this.x:this.x+this.y},this.abbrev=function(){var e=this.x.substr(0,1),t=this.y.substr(0,1);return e===t?e:this.precedance===a?t+e:e+t},this.invertx=function(e){this.x=this.x===h?d:this.x===d?h:e||this.x},this.inverty=function(e){this.y=this.y===c?p:this.y===p?c:e||this.y},this.clone=function(){return{x:this.x,y:this.y,precedance:this.precedance,string:this.string,abbrev:this.abbrev,clone:this.clone,invertx:this.invertx,inverty:this.inverty}}},offset:function(e,n){function c(e,t){i.left+=t*e.scrollLeft(),i.top+=t*e.scrollTop()}var i=e.offset(),s=e.closest("body"),o=r.browser.msie&&t.compatMode!=="CSS1Compat",u=n,a,f,l;if(u){do u.css("position")!=="static"&&(f=u.position(),i.left-=f.left+(parseInt(u.css("borderLeftWidth"),10)||0)+(parseInt(u.css("marginLeft"),10)||0),i.top-=f.top+(parseInt(u.css("borderTopWidth"),10)||0)+(parseInt(u.css("marginTop"),10)||0),!a&&(l=u.css("overflow"))!=="hidden"&&l!=="visible"&&(a=u));while((u=r(u[0].offsetParent)).length);(a&&a[0]!==s[0]||o)&&c(a||s,1)}return i},iOS:parseFloat((""+(/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))||s,fn:{attr:function(e,t){if(this.length){var n=this[0],i="title",s=r.data(n,"qtip");if(e===i&&s&&"object"==typeof s&&s.options.suppress)return arguments.length<2?r.attr(n,O):(s&&s.options.content.attr===i&&s.cache.attr&&s.set("content.text",t),this.attr(O,t))}return r.fn["attr"+A].apply(this,arguments)},clone:function(e){var t=r([]),n="title",i=r.fn["clone"+A].apply(this,arguments);return e||i.filter("["+O+"]").attr("title",function(){return r.attr(this,O)}).removeAttr(O),i}}},r.each(w.fn,function(e,t){if(!t||r.fn[e+A])return i;var n=r.fn[e+A]=r.fn[e];r.fn[e]=function(){return t.apply(this,arguments)||n.apply(this,arguments)}}),r.ui||(r["cleanData"+A]=r.cleanData,r.cleanData=function(e){for(var t=0,i;(i=e[t])!==n;t++)try{r(i).triggerHandler("removeqtip")}catch(s){}r["cleanData"+A](e)}),b.version="2.0.1",b.nextid=0,b.inactiveEvents="click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(" "),b.zindex=15e3,b.defaults={prerender:s,id:s,overwrite:i,suppress:i,content:{text:i,attr:"title",deferred:s,title:{text:s,button:s}},position:{my:"top left",at:"bottom right",target:s,container:s,viewport:s,adjust:{x:0,y:0,mouse:i,resize:i,method:"flipinvert flipinvert"},effect:function(e,t,n){r(this).animate(t,{duration:200,queue:s})}},show:{target:s,event:"mouseenter",effect:i,delay:90,solo:s,ready:s,autofocus:s},hide:{target:s,event:"mouseleave",effect:i,delay:0,fixed:s,inactive:s,leave:"window",distance:s},style:{classes:"",widget:s,width:s,height:s,def:i},events:{render:o,move:o,show:o,hide:o,toggle:o,visible:o,hidden:o,focus:o,blur:o}},w.svg=function(e,n,i,s){var o=r(t),u=n[0],a={width:0,height:0,position:{top:1e10,left:1e10}},f,l,c,h,p;while(!u.getBBox)u=u.parentNode;if(u.getBBox&&u.parentNode){f=u.getBBox(),l=u.getScreenCTM(),c=u.farthestViewportElement||u;if(!c.createSVGPoint)return a;h=c.createSVGPoint(),h.x=f.x,h.y=f.y,p=h.matrixTransform(l),a.position.left=p.x,a.position.top=p.y,h.x+=f.width,h.y+=f.height,p=h.matrixTransform(l),a.width=p.x-a.position.left,a.height=p.y-a.position.top,a.position.left+=o.scrollLeft(),a.position.top+=o.scrollTop()}return a},w.ajax=function(e){var t=e.plugins.ajax;return"object"==typeof t?t:e.plugins.ajax=new B(e)},w.ajax.initialize="render",w.ajax.sanitize=function(e){var t=e.content,n;t&&"ajax"in t&&(n=t.ajax,typeof n!="object"&&(n=e.content.ajax={url:n}),"boolean"!=typeof n.once&&n.once&&(n.once=!!n.once))},r.extend(i,b.defaults,{content:{ajax:{loading:i,once:i}}}),w.tip=function(e){var t=e.plugins.tip;return"object"==typeof t?t:e.plugins.tip=new F(e)},w.tip.initialize="render",w.tip.sanitize=function(e){var t=e.style,n;t&&"tip"in t&&(n=e.style.tip,typeof n!="object"&&(e.style.tip={corner:n}),/string|boolean/i.test(typeof n.corner)||(n.corner=i),typeof n.width!="number"&&delete n.width,typeof n.height!="number"&&delete n.height,typeof n.border!="number"&&n.border!==i&&delete n.border,typeof n.offset!="number"&&delete n.offset)},r.extend(i,b.defaults,{style:{tip:{corner:i,mimic:s,width:6,height:6,border:i,offset:0}}}),w.modal=function(e){var t=e.plugins.modal;return"object"==typeof t?t:e.plugins.modal=new I(e)},w.modal.initialize="render",w.modal.sanitize=function(e){e.show&&(typeof e.show.modal!="object"?e.show.modal={on:!!e.show.modal}:typeof e.show.modal.on=="undefined"&&(e.show.modal.on=i))},w.modal.zindex=b.zindex-200,w.modal.focusable=["a[href]","area[href]","input","select","textarea","button","iframe","object","embed","[tabindex]","[contenteditable]"],r.extend(i,b.defaults,{show:{modal:{on:s,effect:i,blur:i,stealfocus:i,escape:i}}}),w.viewport=function(n,r,i,s,o,m,b){function j(e,t,n,i,s,o,u,a,f){var l=r[s],c=x[e],h=T[e],p=n===y,d=-O.offset[s]+A.offset[s]+A["scroll"+s],m=c===s?f:c===o?-f:-f/2,b=h===s?a:h===o?-a:-a/2,w=_&&_.size?_.size[u]||0:0,E=_&&_.corner&&_.corner.precedance===e&&!p?w:0,S=d-l+E,N=l+f-A[u]-d+E,C=m-(x.precedance===e||c===x[t]?b:0)-(h===v?a/2:0);return p?(E=_&&_.corner&&_.corner.precedance===t?w:0,C=(c===s?1:-1)*m-E,r[s]+=S>0?S:N>0?-N:0,r[s]=Math.max(-O.offset[s]+A.offset[s]+(E&&_.corner[e]===v?_.offset:0),l-C,Math.min(Math.max(-O.offset[s]+A.offset[s]+A[u],l+C),r[s]))):(i*=n===g?2:0,S>0&&(c!==s||N>0)?(r[s]-=C+i,H["invert"+e](s)):N>0&&(c!==o||S>0)&&(r[s]-=(c===v?-C:C)+i,H["invert"+e](o)),r[s]N&&(r[s]=l,H=x.clone())),r[s]-l}var w=i.target,E=n.elements.tooltip,x=i.my,T=i.at,N=i.adjust,C=N.method.split(" "),k=C[0],L=C[1]||C[0],A=i.viewport,O=i.container,M=n.cache,_=n.plugins.tip,D={left:0,top:0},P,H,B;if(!A.jquery||w[0]===e||w[0]===t.body||N.method==="none")return D;P=E.css("position")==="fixed",A={elem:A,height:A[(A[0]===e?"h":"outerH")+"eight"](),width:A[(A[0]===e?"w":"outerW")+"idth"](),scrollleft:P?0:A.scrollLeft(),scrolltop:P?0:A.scrollTop(),offset:A.offset()||{left:0,top:0}},O={elem:O,scrollLeft:O.scrollLeft(),scrollTop:O.scrollTop(),offset:O.offset()||{left:0,top:0}};if(k!=="shift"||L!=="shift")H=x.clone();return D={left:k!=="none"?j(u,a,k,N.x,h,d,f,s,m):0,top:L!=="none"?j(a,u,L,N.y,c,p,l,o,b):0},H&&M.lastClass!==(B=S+"-pos-"+H.abbrev())&&E.removeClass(n.cache.lastClass).addClass(n.cache.lastClass=B),D},w.imagemap=function(e,t,n,i){function E(e,t,n){var r=0,i=1,s=1,o=0,u=0,a=e.width,f=e.height;while(a>0&&f>0&&i>0&&s>0){a=Math.floor(a/2),f=Math.floor(f/2),n.x===h?i=a:n.x===d?i=e.width-a:i+=Math.floor(a/2),n.y===c?s=f:n.y===p?s=e.height-f:s+=Math.floor(f/2),r=t.length;while(r--){if(t.length<2)break;o=t[r][0]-e.position.left,u=t[r][1]-e.position.top,(n.x===h&&o>=i||n.x===d&&o<=i||n.x===v&&(oe.width-i)||n.y===c&&u>=s||n.y===p&&u<=s||n.y===v&&(ue.height-s))&&t.splice(r,1)}}return{left:t[0][0],top:t[0][1]}}t.jquery||(t=r(t));var s=e.cache.areas={},o=(t[0].shape||t.attr("shape")).toLowerCase(),u=t[0].coords||t.attr("coords"),a=u.split(","),f=[],l=r('img[usemap="#'+t.parent("map").attr("name")+'"]'),m=l.offset(),g={width:0,height:0,position:{top:1e10,right:0,bottom:0,left:1e10}},y=0,b=0,w;m.left+=Math.ceil((l.outerWidth()-l.width())/2),m.top+=Math.ceil((l.outerHeight()-l.height())/2);if(o==="poly"){y=a.length;while(y--)b=[parseInt(a[--y],10),parseInt(a[y+1],10)],b[0]>g.position.right&&(g.position.right=b[0]),b[0]g.position.bottom&&(g.position.bottom=b[1]),b[1]0?setTimeout(d.proxy(a,this),b):void a.call(this)}function m(a){this.tooltip.hasClass(ab)||(clearTimeout(this.timers.show),clearTimeout(this.timers.hide),this.timers.show=l.call(this,function(){this.toggle(D,a)},this.options.show.delay))}function n(a){if(!this.tooltip.hasClass(ab)&&!this.destroyed){var b=d(a.relatedTarget),c=b.closest(W)[0]===this.tooltip[0],e=b[0]===this.options.show.target[0];if(clearTimeout(this.timers.show),clearTimeout(this.timers.hide),this!==b[0]&&"mouse"===this.options.position.target&&c||this.options.hide.fixed&&/mouse(out|leave|move)/.test(a.type)&&(c||e))try{a.preventDefault(),a.stopImmediatePropagation()}catch(f){}else this.timers.hide=l.call(this,function(){this.toggle(E,a)},this.options.hide.delay,this)}}function o(a){!this.tooltip.hasClass(ab)&&this.options.hide.inactive&&(clearTimeout(this.timers.inactive),this.timers.inactive=l.call(this,function(){this.hide(a)},this.options.hide.inactive))}function p(a){this.rendered&&this.tooltip[0].offsetWidth>0&&this.reposition(a)}function q(a,c,e){d(b.body).delegate(a,(c.split?c:c.join("."+S+" "))+"."+S,function(){var a=y.api[d.attr(this,U)];a&&!a.disabled&&e.apply(a,arguments)})}function r(a,c,f){var g,i,j,k,l,m=d(b.body),n=a[0]===b?m:a,o=a.metadata?a.metadata(f.metadata):F,p="html5"===f.metadata.type&&o?o[f.metadata.name]:F,q=a.data(f.metadata.name||"qtipopts");try{q="string"==typeof q?d.parseJSON(q):q}catch(r){}if(k=d.extend(D,{},y.defaults,f,"object"==typeof q?h(q):F,h(p||o)),i=k.position,k.id=c,"boolean"==typeof k.content.text){if(j=a.attr(k.content.attr),k.content.attr===E||!j)return E;k.content.text=j}if(i.container.length||(i.container=m),i.target===E&&(i.target=n),k.show.target===E&&(k.show.target=n),k.show.solo===D&&(k.show.solo=i.container.closest("body")),k.hide.target===E&&(k.hide.target=n),k.position.viewport===D&&(k.position.viewport=i.container),i.container=i.container.eq(0),i.at=new A(i.at,D),i.my=new A(i.my),a.data(S))if(k.overwrite)a.qtip("destroy",!0);else if(k.overwrite===E)return E;return a.attr(T,c),k.suppress&&(l=a.attr("title"))&&a.removeAttr("title").attr(cb,l).attr("title",""),g=new e(a,k,c,!!j),a.data(S,g),g}function s(a){return a.charAt(0).toUpperCase()+a.slice(1)}function t(a,b){var d,e,f=b.charAt(0).toUpperCase()+b.slice(1),g=(b+" "+rb.join(f+" ")+f).split(" "),h=0;if(qb[b])return a.css(qb[b]);for(;d=g[h++];)if((e=a.css(d))!==c)return qb[b]=d,e}function u(a,b){return Math.ceil(parseFloat(t(a,b)))}function v(a,b){this._ns="tip",this.options=b,this.offset=b.offset,this.size=[b.width,b.height],this.init(this.qtip=a)}function w(a,b){this.options=b,this._ns="-modal",this.init(this.qtip=a)}function x(a){this._ns="ie6",this.init(this.qtip=a)}var y,z,A,B,C,D=!0,E=!1,F=null,G="x",H="y",I="width",J="height",K="top",L="left",M="bottom",N="right",O="center",P="flipinvert",Q="shift",R={},S="qtip",T="data-hasqtip",U="data-qtip-id",V=["ui-widget","ui-tooltip"],W="."+S,X="click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(" "),Y=S+"-fixed",Z=S+"-default",$=S+"-focus",_=S+"-hover",ab=S+"-disabled",bb="_replacedByqTip",cb="oldtitle",db={ie:function(){for(var a=4,c=b.createElement("div");(c.innerHTML="")&&c.getElementsByTagName("i")[0];a+=1);return a>4?a:0/0}(),iOS:parseFloat((""+(/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))||E};z=e.prototype,z._when=function(a){return d.when.apply(d,a)},z.render=function(a){if(this.rendered||this.destroyed)return this;var b,c=this,e=this.options,f=this.cache,g=this.elements,h=e.content.text,i=e.content.title,j=e.content.button,k=e.position,l=("."+this._id+" ",[]);return d.attr(this.target[0],"aria-describedby",this._id),f.posClass=this._createPosClass((this.position={my:k.my,at:k.at}).my),this.tooltip=g.tooltip=b=d("
    ",{id:this._id,"class":[S,Z,e.style.classes,f.posClass].join(" "),width:e.style.width||"",height:e.style.height||"",tracking:"mouse"===k.target&&k.adjust.mouse,role:"alert","aria-live":"polite","aria-atomic":E,"aria-describedby":this._id+"-content","aria-hidden":D}).toggleClass(ab,this.disabled).attr(U,this.id).data(S,this).appendTo(k.container).append(g.content=d("
    ",{"class":S+"-content",id:this._id+"-content","aria-atomic":D})),this.rendered=-1,this.positioning=D,i&&(this._createTitle(),d.isFunction(i)||l.push(this._updateTitle(i,E))),j&&this._createButton(),d.isFunction(h)||l.push(this._updateContent(h,E)),this.rendered=D,this._setWidget(),d.each(R,function(a){var b;"render"===this.initialize&&(b=this(c))&&(c.plugins[a]=b)}),this._unassignEvents(),this._assignEvents(),this._when(l).then(function(){c._trigger("render"),c.positioning=E,c.hiddenDuringWait||!e.show.ready&&!a||c.toggle(D,f.event,E),c.hiddenDuringWait=E}),y.api[this.id]=this,this},z.destroy=function(a){function b(){if(!this.destroyed){this.destroyed=D;var a,b=this.target,c=b.attr(cb);this.rendered&&this.tooltip.stop(1,0).find("*").remove().end().remove(),d.each(this.plugins,function(){this.destroy&&this.destroy()});for(a in this.timers)clearTimeout(this.timers[a]);b.removeData(S).removeAttr(U).removeAttr(T).removeAttr("aria-describedby"),this.options.suppress&&c&&b.attr("title",c).removeAttr(cb),this._unassignEvents(),this.options=this.elements=this.cache=this.timers=this.plugins=this.mouse=F,delete y.api[this.id]}}return this.destroyed?this.target:(a===D&&"hide"!==this.triggering||!this.rendered?b.call(this):(this.tooltip.one("tooltiphidden",d.proxy(b,this)),!this.triggering&&this.hide()),this.target)},B=z.checks={builtin:{"^id$":function(a,b,c,e){var f=c===D?y.nextid:c,g=S+"-"+f;f!==E&&f.length>0&&!d("#"+g).length?(this._id=g,this.rendered&&(this.tooltip[0].id=this._id,this.elements.content[0].id=this._id+"-content",this.elements.title[0].id=this._id+"-title")):a[b]=e},"^prerender":function(a,b,c){c&&!this.rendered&&this.render(this.options.show.ready)},"^content.text$":function(a,b,c){this._updateContent(c)},"^content.attr$":function(a,b,c,d){this.options.content.text===this.target.attr(d)&&this._updateContent(this.target.attr(c))},"^content.title$":function(a,b,c){return c?(c&&!this.elements.title&&this._createTitle(),void this._updateTitle(c)):this._removeTitle()},"^content.button$":function(a,b,c){this._updateButton(c)},"^content.title.(text|button)$":function(a,b,c){this.set("content."+b,c)},"^position.(my|at)$":function(a,b,c){"string"==typeof c&&(this.position[b]=a[b]=new A(c,"at"===b))},"^position.container$":function(a,b,c){this.rendered&&this.tooltip.appendTo(c)},"^show.ready$":function(a,b,c){c&&(!this.rendered&&this.render(D)||this.toggle(D))},"^style.classes$":function(a,b,c,d){this.rendered&&this.tooltip.removeClass(d).addClass(c)},"^style.(width|height)":function(a,b,c){this.rendered&&this.tooltip.css(b,c)},"^style.widget|content.title":function(){this.rendered&&this._setWidget()},"^style.def":function(a,b,c){this.rendered&&this.tooltip.toggleClass(Z,!!c)},"^events.(render|show|move|hide|focus|blur)$":function(a,b,c){this.rendered&&this.tooltip[(d.isFunction(c)?"":"un")+"bind"]("tooltip"+b,c)},"^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)":function(){if(this.rendered){var a=this.options.position;this.tooltip.attr("tracking","mouse"===a.target&&a.adjust.mouse),this._unassignEvents(),this._assignEvents()}}}},z.get=function(a){if(this.destroyed)return this;var b=i(this.options,a.toLowerCase()),c=b[0][b[1]];return c.precedance?c.string():c};var eb=/^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,fb=/^prerender|show\.ready/i;z.set=function(a,b){if(this.destroyed)return this;{var c,e=this.rendered,f=E,g=this.options;this.checks}return"string"==typeof a?(c=a,a={},a[c]=b):a=d.extend({},a),d.each(a,function(b,c){if(e&&fb.test(b))return void delete a[b];var h,j=i(g,b.toLowerCase());h=j[0][j[1]],j[0][j[1]]=c&&c.nodeType?d(c):c,f=eb.test(b)||f,a[b]=[j[0],j[1],c,h]}),h(g),this.positioning=D,d.each(a,d.proxy(j,this)),this.positioning=E,this.rendered&&this.tooltip[0].offsetWidth>0&&f&&this.reposition("mouse"===g.position.target?F:this.cache.event),this},z._update=function(a,b){var c=this,e=this.cache;return this.rendered&&a?(d.isFunction(a)&&(a=a.call(this.elements.target,e.event,this)||""),d.isFunction(a.then)?(e.waiting=D,a.then(function(a){return e.waiting=E,c._update(a,b)},F,function(a){return c._update(a,b)})):a===E||!a&&""!==a?E:(a.jquery&&a.length>0?b.empty().append(a.css({display:"block",visibility:"visible"})):b.html(a),this._waitForContent(b).then(function(a){c.rendered&&c.tooltip[0].offsetWidth>0&&c.reposition(e.event,!a.length)}))):E},z._waitForContent=function(a){var b=this.cache;return b.waiting=D,(d.fn.imagesLoaded?a.imagesLoaded():d.Deferred().resolve([])).done(function(){b.waiting=E}).promise()},z._updateContent=function(a,b){this._update(a,this.elements.content,b)},z._updateTitle=function(a,b){this._update(a,this.elements.title,b)===E&&this._removeTitle(E)},z._createTitle=function(){var a=this.elements,b=this._id+"-title";a.titlebar&&this._removeTitle(),a.titlebar=d("
    ",{"class":S+"-titlebar "+(this.options.style.widget?k("header"):"")}).append(a.title=d("
    ",{id:b,"class":S+"-title","aria-atomic":D})).insertBefore(a.content).delegate(".qtip-close","mousedown keydown mouseup keyup mouseout",function(a){d(this).toggleClass("ui-state-active ui-state-focus","down"===a.type.substr(-4))}).delegate(".qtip-close","mouseover mouseout",function(a){d(this).toggleClass("ui-state-hover","mouseover"===a.type)}),this.options.content.button&&this._createButton()},z._removeTitle=function(a){var b=this.elements;b.title&&(b.titlebar.remove(),b.titlebar=b.title=b.button=F,a!==E&&this.reposition())},z._createPosClass=function(a){return S+"-pos-"+(a||this.options.position.my).abbrev()},z.reposition=function(c,e){if(!this.rendered||this.positioning||this.destroyed)return this;this.positioning=D;var f,g,h,i,j=this.cache,k=this.tooltip,l=this.options.position,m=l.target,n=l.my,o=l.at,p=l.viewport,q=l.container,r=l.adjust,s=r.method.split(" "),t=k.outerWidth(E),u=k.outerHeight(E),v=0,w=0,x=k.css("position"),y={left:0,top:0},z=k[0].offsetWidth>0,A=c&&"scroll"===c.type,B=d(a),C=q[0].ownerDocument,F=this.mouse;if(d.isArray(m)&&2===m.length)o={x:L,y:K},y={left:m[0],top:m[1]};else if("mouse"===m)o={x:L,y:K},(!r.mouse||this.options.hide.distance)&&j.origin&&j.origin.pageX?c=j.origin:!c||c&&("resize"===c.type||"scroll"===c.type)?c=j.event:F&&F.pageX&&(c=F),"static"!==x&&(y=q.offset()),C.body.offsetWidth!==(a.innerWidth||C.documentElement.clientWidth)&&(g=d(b.body).offset()),y={left:c.pageX-y.left+(g&&g.left||0),top:c.pageY-y.top+(g&&g.top||0)},r.mouse&&A&&F&&(y.left-=(F.scrollX||0)-B.scrollLeft(),y.top-=(F.scrollY||0)-B.scrollTop());else{if("event"===m?c&&c.target&&"scroll"!==c.type&&"resize"!==c.type?j.target=d(c.target):c.target||(j.target=this.elements.target):"event"!==m&&(j.target=d(m.jquery?m:this.elements.target)),m=j.target,m=d(m).eq(0),0===m.length)return this;m[0]===b||m[0]===a?(v=db.iOS?a.innerWidth:m.width(),w=db.iOS?a.innerHeight:m.height(),m[0]===a&&(y={top:(p||m).scrollTop(),left:(p||m).scrollLeft()})):R.imagemap&&m.is("area")?f=R.imagemap(this,m,o,R.viewport?s:E):R.svg&&m&&m[0].ownerSVGElement?f=R.svg(this,m,o,R.viewport?s:E):(v=m.outerWidth(E),w=m.outerHeight(E),y=m.offset()),f&&(v=f.width,w=f.height,g=f.offset,y=f.position),y=this.reposition.offset(m,y,q),(db.iOS>3.1&&db.iOS<4.1||db.iOS>=4.3&&db.iOS<4.33||!db.iOS&&"fixed"===x)&&(y.left-=B.scrollLeft(),y.top-=B.scrollTop()),(!f||f&&f.adjustable!==E)&&(y.left+=o.x===N?v:o.x===O?v/2:0,y.top+=o.y===M?w:o.y===O?w/2:0)}return y.left+=r.x+(n.x===N?-t:n.x===O?-t/2:0),y.top+=r.y+(n.y===M?-u:n.y===O?-u/2:0),R.viewport?(h=y.adjusted=R.viewport(this,y,l,v,w,t,u),g&&h.left&&(y.left+=g.left),g&&h.top&&(y.top+=g.top),h.my&&(this.position.my=h.my)):y.adjusted={left:0,top:0},j.posClass!==(i=this._createPosClass(this.position.my))&&k.removeClass(j.posClass).addClass(j.posClass=i),this._trigger("move",[y,p.elem||p],c)?(delete y.adjusted,e===E||!z||isNaN(y.left)||isNaN(y.top)||"mouse"===m||!d.isFunction(l.effect)?k.css(y):d.isFunction(l.effect)&&(l.effect.call(k,this,d.extend({},y)),k.queue(function(a){d(this).css({opacity:"",height:""}),db.ie&&this.style.removeAttribute("filter"),a()})),this.positioning=E,this):this},z.reposition.offset=function(a,c,e){function f(a,b){c.left+=b*a.scrollLeft(),c.top+=b*a.scrollTop()}if(!e[0])return c;var g,h,i,j,k=d(a[0].ownerDocument),l=!!db.ie&&"CSS1Compat"!==b.compatMode,m=e[0];do"static"!==(h=d.css(m,"position"))&&("fixed"===h?(i=m.getBoundingClientRect(),f(k,-1)):(i=d(m).position(),i.left+=parseFloat(d.css(m,"borderLeftWidth"))||0,i.top+=parseFloat(d.css(m,"borderTopWidth"))||0),c.left-=i.left+(parseFloat(d.css(m,"marginLeft"))||0),c.top-=i.top+(parseFloat(d.css(m,"marginTop"))||0),g||"hidden"===(j=d.css(m,"overflow"))||"visible"===j||(g=d(m)));while(m=m.offsetParent);return g&&(g[0]!==k[0]||l)&&f(g,1),c};var gb=(A=z.reposition.Corner=function(a,b){a=(""+a).replace(/([A-Z])/," $1").replace(/middle/gi,O).toLowerCase(),this.x=(a.match(/left|right/i)||a.match(/center/)||["inherit"])[0].toLowerCase(),this.y=(a.match(/top|bottom|center/i)||["inherit"])[0].toLowerCase(),this.forceY=!!b;var c=a.charAt(0);this.precedance="t"===c||"b"===c?H:G}).prototype;gb.invert=function(a,b){this[a]=this[a]===L?N:this[a]===N?L:b||this[a]},gb.string=function(a){var b=this.x,c=this.y,d=b!==c?"center"===b||"center"!==c&&(this.precedance===H||this.forceY)?[c,b]:[b,c]:[b];return a!==!1?d.join(" "):d},gb.abbrev=function(){var a=this.string(!1);return a[0].charAt(0)+(a[1]&&a[1].charAt(0)||"")},gb.clone=function(){return new A(this.string(),this.forceY)},z.toggle=function(a,c){var e=this.cache,f=this.options,g=this.tooltip;if(c){if(/over|enter/.test(c.type)&&e.event&&/out|leave/.test(e.event.type)&&f.show.target.add(c.target).length===f.show.target.length&&g.has(c.relatedTarget).length)return this;e.event=d.event.fix(c)}if(this.waiting&&!a&&(this.hiddenDuringWait=D),!this.rendered)return a?this.render(1):this;if(this.destroyed||this.disabled)return this;var h,i,j,k=a?"show":"hide",l=this.options[k],m=(this.options[a?"hide":"show"],this.options.position),n=this.options.content,o=this.tooltip.css("width"),p=this.tooltip.is(":visible"),q=a||1===l.target.length,r=!c||l.target.length<2||e.target[0]===c.target;return(typeof a).search("boolean|number")&&(a=!p),h=!g.is(":animated")&&p===a&&r,i=h?F:!!this._trigger(k,[90]),this.destroyed?this:(i!==E&&a&&this.focus(c),!i||h?this:(d.attr(g[0],"aria-hidden",!a),a?(this.mouse&&(e.origin=d.event.fix(this.mouse)),d.isFunction(n.text)&&this._updateContent(n.text,E),d.isFunction(n.title)&&this._updateTitle(n.title,E),!C&&"mouse"===m.target&&m.adjust.mouse&&(d(b).bind("mousemove."+S,this._storeMouse),C=D),o||g.css("width",g.outerWidth(E)),this.reposition(c,arguments[2]),o||g.css("width",""),l.solo&&("string"==typeof l.solo?d(l.solo):d(W,l.solo)).not(g).not(l.target).qtip("hide",d.Event("tooltipsolo"))):(clearTimeout(this.timers.show),delete e.origin,C&&!d(W+'[tracking="true"]:visible',l.solo).not(g).length&&(d(b).unbind("mousemove."+S),C=E),this.blur(c)),j=d.proxy(function(){a?(db.ie&&g[0].style.removeAttribute("filter"),g.css("overflow",""),"string"==typeof l.autofocus&&d(this.options.show.autofocus,g).focus(),this.options.show.target.trigger("qtip-"+this.id+"-inactive")):g.css({display:"",visibility:"",opacity:"",left:"",top:""}),this._trigger(a?"visible":"hidden")},this),l.effect===E||q===E?(g[k](),j()):d.isFunction(l.effect)?(g.stop(1,1),l.effect.call(g,this),g.queue("fx",function(a){j(),a()})):g.fadeTo(90,a?1:0,j),a&&l.target.trigger("qtip-"+this.id+"-inactive"),this))},z.show=function(a){return this.toggle(D,a)},z.hide=function(a){return this.toggle(E,a)},z.focus=function(a){if(!this.rendered||this.destroyed)return this;var b=d(W),c=this.tooltip,e=parseInt(c[0].style.zIndex,10),f=y.zindex+b.length;return c.hasClass($)||this._trigger("focus",[f],a)&&(e!==f&&(b.each(function(){this.style.zIndex>e&&(this.style.zIndex=this.style.zIndex-1)}),b.filter("."+$).qtip("blur",a)),c.addClass($)[0].style.zIndex=f),this},z.blur=function(a){return!this.rendered||this.destroyed?this:(this.tooltip.removeClass($),this._trigger("blur",[this.tooltip.css("zIndex")],a),this)},z.disable=function(a){return this.destroyed?this:("toggle"===a?a=!(this.rendered?this.tooltip.hasClass(ab):this.disabled):"boolean"!=typeof a&&(a=D),this.rendered&&this.tooltip.toggleClass(ab,a).attr("aria-disabled",a),this.disabled=!!a,this)},z.enable=function(){return this.disable(E)},z._createButton=function(){var a=this,b=this.elements,c=b.tooltip,e=this.options.content.button,f="string"==typeof e,g=f?e:"Close tooltip";b.button&&b.button.remove(),b.button=e.jquery?e:d("",{"class":"qtip-close "+(this.options.style.widget?"":S+"-icon"),title:g,"aria-label":g}).prepend(d("",{"class":"ui-icon ui-icon-close",html:"×"})),b.button.appendTo(b.titlebar||c).attr("role","button").click(function(b){return c.hasClass(ab)||a.hide(b),E})},z._updateButton=function(a){if(!this.rendered)return E;var b=this.elements.button;a?this._createButton():b.remove()},z._setWidget=function(){var a=this.options.style.widget,b=this.elements,c=b.tooltip,d=c.hasClass(ab);c.removeClass(ab),ab=a?"ui-state-disabled":"qtip-disabled",c.toggleClass(ab,d),c.toggleClass("ui-helper-reset "+k(),a).toggleClass(Z,this.options.style.def&&!a),b.content&&b.content.toggleClass(k("content"),a),b.titlebar&&b.titlebar.toggleClass(k("header"),a),b.button&&b.button.toggleClass(S+"-icon",!a)},z._storeMouse=function(a){return(this.mouse=d.event.fix(a)).type="mousemove",this},z._bind=function(a,b,c,e,f){if(a&&c&&b.length){var g="."+this._id+(e?"-"+e:"");return d(a).bind((b.split?b:b.join(g+" "))+g,d.proxy(c,f||this)),this}},z._unbind=function(a,b){return a&&d(a).unbind("."+this._id+(b?"-"+b:"")),this},z._trigger=function(a,b,c){var e=d.Event("tooltip"+a);return e.originalEvent=c&&d.extend({},c)||this.cache.event||F,this.triggering=a,this.tooltip.trigger(e,[this].concat(b||[])),this.triggering=E,!e.isDefaultPrevented()},z._bindEvents=function(a,b,c,e,f,g){var h=c.filter(e).add(e.filter(c)),i=[];h.length&&(d.each(b,function(b,c){var e=d.inArray(c,a);e>-1&&i.push(a.splice(e,1)[0])}),i.length&&(this._bind(h,i,function(a){var b=this.rendered?this.tooltip[0].offsetWidth>0:!1;(b?g:f).call(this,a)}),c=c.not(h),e=e.not(h))),this._bind(c,a,f),this._bind(e,b,g)},z._assignInitialEvents=function(a){function b(a){return this.disabled||this.destroyed?E:(this.cache.event=a&&d.event.fix(a),this.cache.target=a&&d(a.target),clearTimeout(this.timers.show),void(this.timers.show=l.call(this,function(){this.render("object"==typeof a||c.show.ready)},c.prerender?0:c.show.delay)))}var c=this.options,e=c.show.target,f=c.hide.target,g=c.show.event?d.trim(""+c.show.event).split(" "):[],h=c.hide.event?d.trim(""+c.hide.event).split(" "):[];this._bind(this.elements.target,["remove","removeqtip"],function(){this.destroy(!0)},"destroy"),/mouse(over|enter)/i.test(c.show.event)&&!/mouse(out|leave)/i.test(c.hide.event)&&h.push("mouseleave"),this._bind(e,"mousemove",function(a){this._storeMouse(a),this.cache.onTarget=D}),this._bindEvents(g,h,e,f,b,function(){return this.timers?void clearTimeout(this.timers.show):E}),(c.show.ready||c.prerender)&&b.call(this,a)},z._assignEvents=function(){var c=this,e=this.options,f=e.position,g=this.tooltip,h=e.show.target,i=e.hide.target,j=f.container,k=f.viewport,l=d(b),q=(d(b.body),d(a)),r=e.show.event?d.trim(""+e.show.event).split(" "):[],s=e.hide.event?d.trim(""+e.hide.event).split(" "):[];d.each(e.events,function(a,b){c._bind(g,"toggle"===a?["tooltipshow","tooltiphide"]:["tooltip"+a],b,null,g)}),/mouse(out|leave)/i.test(e.hide.event)&&"window"===e.hide.leave&&this._bind(l,["mouseout","blur"],function(a){/select|option/.test(a.target.nodeName)||a.relatedTarget||this.hide(a)}),e.hide.fixed?i=i.add(g.addClass(Y)):/mouse(over|enter)/i.test(e.show.event)&&this._bind(i,"mouseleave",function(){clearTimeout(this.timers.show)}),(""+e.hide.event).indexOf("unfocus")>-1&&this._bind(j.closest("html"),["mousedown","touchstart"],function(a){var b=d(a.target),c=this.rendered&&!this.tooltip.hasClass(ab)&&this.tooltip[0].offsetWidth>0,e=b.parents(W).filter(this.tooltip[0]).length>0;b[0]===this.target[0]||b[0]===this.tooltip[0]||e||this.target.has(b[0]).length||!c||this.hide(a)}),"number"==typeof e.hide.inactive&&(this._bind(h,"qtip-"+this.id+"-inactive",o,"inactive"),this._bind(i.add(g),y.inactiveEvents,o)),this._bindEvents(r,s,h,i,m,n),this._bind(h.add(g),"mousemove",function(a){if("number"==typeof e.hide.distance){var b=this.cache.origin||{},c=this.options.hide.distance,d=Math.abs;(d(a.pageX-b.pageX)>=c||d(a.pageY-b.pageY)>=c)&&this.hide(a)}this._storeMouse(a)}),"mouse"===f.target&&f.adjust.mouse&&(e.hide.event&&this._bind(h,["mouseenter","mouseleave"],function(a){return this.cache?void(this.cache.onTarget="mouseenter"===a.type):E}),this._bind(l,"mousemove",function(a){this.rendered&&this.cache.onTarget&&!this.tooltip.hasClass(ab)&&this.tooltip[0].offsetWidth>0&&this.reposition(a)})),(f.adjust.resize||k.length)&&this._bind(d.event.special.resize?k:q,"resize",p),f.adjust.scroll&&this._bind(q.add(f.container),"scroll",p)},z._unassignEvents=function(){var c=this.options,e=c.show.target,f=c.hide.target,g=d.grep([this.elements.target[0],this.rendered&&this.tooltip[0],c.position.container[0],c.position.viewport[0],c.position.container.closest("html")[0],a,b],function(a){return"object"==typeof a});e&&e.toArray&&(g=g.concat(e.toArray())),f&&f.toArray&&(g=g.concat(f.toArray())),this._unbind(g)._unbind(g,"destroy")._unbind(g,"inactive")},d(function(){q(W,["mouseenter","mouseleave"],function(a){var b="mouseenter"===a.type,c=d(a.currentTarget),e=d(a.relatedTarget||a.target),f=this.options;b?(this.focus(a),c.hasClass(Y)&&!c.hasClass(ab)&&clearTimeout(this.timers.hide)):"mouse"===f.position.target&&f.position.adjust.mouse&&f.hide.event&&f.show.target&&!e.closest(f.show.target[0]).length&&this.hide(a),c.toggleClass(_,b)}),q("["+U+"]",X,o)}),y=d.fn.qtip=function(a,b,e){var f=(""+a).toLowerCase(),g=F,i=d.makeArray(arguments).slice(1),j=i[i.length-1],k=this[0]?d.data(this[0],S):F;return!arguments.length&&k||"api"===f?k:"string"==typeof a?(this.each(function(){var a=d.data(this,S);if(!a)return D;if(j&&j.timeStamp&&(a.cache.event=j),!b||"option"!==f&&"options"!==f)a[f]&&a[f].apply(a,i);else{if(e===c&&!d.isPlainObject(b))return g=a.get(b),E;a.set(b,e)}}),g!==F?g:this):"object"!=typeof a&&arguments.length?void 0:(k=h(d.extend(D,{},a)),this.each(function(a){var b,c;return c=d.isArray(k.id)?k.id[a]:k.id,c=!c||c===E||c.length<1||y.api[c]?y.nextid++:c,b=r(d(this),c,k),b===E?D:(y.api[c]=b,d.each(R,function(){"initialize"===this.initialize&&this(b)}),void b._assignInitialEvents(j))}))},d.qtip=e,y.api={},d.each({attr:function(a,b){if(this.length){var c=this[0],e="title",f=d.data(c,"qtip");if(a===e&&f&&"object"==typeof f&&f.options.suppress)return arguments.length<2?d.attr(c,cb):(f&&f.options.content.attr===e&&f.cache.attr&&f.set("content.text",b),this.attr(cb,b))}return d.fn["attr"+bb].apply(this,arguments)},clone:function(a){var b=(d([]),d.fn["clone"+bb].apply(this,arguments));return a||b.filter("["+cb+"]").attr("title",function(){return d.attr(this,cb)}).removeAttr(cb),b}},function(a,b){if(!b||d.fn[a+bb])return D;var c=d.fn[a+bb]=d.fn[a];d.fn[a]=function(){return b.apply(this,arguments)||c.apply(this,arguments)}}),d.ui||(d["cleanData"+bb]=d.cleanData,d.cleanData=function(a){for(var b,c=0;(b=d(a[c])).length;c++)if(b.attr(T))try{b.triggerHandler("removeqtip")}catch(e){}d["cleanData"+bb].apply(this,arguments)}),y.version="2.2.1",y.nextid=0,y.inactiveEvents=X,y.zindex=15e3,y.defaults={prerender:E,id:E,overwrite:D,suppress:D,content:{text:D,attr:"title",title:E,button:E},position:{my:"top left",at:"bottom right",target:E,container:E,viewport:E,adjust:{x:0,y:0,mouse:D,scroll:D,resize:D,method:"flipinvert flipinvert"},effect:function(a,b){d(this).animate(b,{duration:200,queue:E})}},show:{target:E,event:"mouseenter",effect:D,delay:90,solo:E,ready:E,autofocus:E},hide:{target:E,event:"mouseleave",effect:D,delay:0,fixed:E,inactive:E,leave:"window",distance:E},style:{classes:"",widget:E,width:E,height:E,def:D},events:{render:F,move:F,show:F,hide:F,toggle:F,visible:F,hidden:F,focus:F,blur:F}};var hb,ib="margin",jb="border",kb="color",lb="background-color",mb="transparent",nb=" !important",ob=!!b.createElement("canvas").getContext,pb=/rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i,qb={},rb=["Webkit","O","Moz","ms"];if(ob)var sb=a.devicePixelRatio||1,tb=function(){var a=b.createElement("canvas").getContext("2d");return a.backingStorePixelRatio||a.webkitBackingStorePixelRatio||a.mozBackingStorePixelRatio||a.msBackingStorePixelRatio||a.oBackingStorePixelRatio||1}(),ub=sb/tb;else var vb=function(a,b,c){return"'};d.extend(v.prototype,{init:function(a){var b,c;c=this.element=a.elements.tip=d("
    ",{"class":S+"-tip"}).prependTo(a.tooltip),ob?(b=d("").appendTo(this.element)[0].getContext("2d"),b.lineJoin="miter",b.miterLimit=1e5,b.save()):(b=vb("shape",'coordorigin="0,0"',"position:absolute;"),this.element.html(b+b),a._bind(d("*",c).add(c),["click","mousedown"],function(a){a.stopPropagation()},this._ns)),a._bind(a.tooltip,"tooltipmove",this.reposition,this._ns,this),this.create()},_swapDimensions:function(){this.size[0]=this.options.height,this.size[1]=this.options.width},_resetDimensions:function(){this.size[0]=this.options.width,this.size[1]=this.options.height},_useTitle:function(a){var b=this.qtip.elements.titlebar;return b&&(a.y===K||a.y===O&&this.element.position().top+this.size[1]/2+this.options.offsetl&&!pb.test(e[1])&&(e[0]=e[1]),this.border=l=p.border!==D?p.border:l):this.border=l=0,k=this.size=this._calculateSize(b),n.css({width:k[0],height:k[1],lineHeight:k[1]+"px"}),j=b.precedance===H?[s(r.x===L?l:r.x===N?k[0]-q[0]-l:(k[0]-q[0])/2),s(r.y===K?k[1]-q[1]:0)]:[s(r.x===L?k[0]-q[0]:0),s(r.y===K?l:r.y===M?k[1]-q[1]-l:(k[1]-q[1])/2)],ob?(g=o[0].getContext("2d"),g.restore(),g.save(),g.clearRect(0,0,6e3,6e3),h=this._calculateTip(r,q,ub),i=this._calculateTip(r,this.size,ub),o.attr(I,k[0]*ub).attr(J,k[1]*ub),o.css(I,k[0]).css(J,k[1]),this._drawCoords(g,i),g.fillStyle=e[1],g.fill(),g.translate(j[0]*ub,j[1]*ub),this._drawCoords(g,h),g.fillStyle=e[0],g.fill()):(h=this._calculateTip(r),h="m"+h[0]+","+h[1]+" l"+h[2]+","+h[3]+" "+h[4]+","+h[5]+" xe",j[2]=l&&/^(r|b)/i.test(b.string())?8===db.ie?2:1:0,o.css({coordsize:k[0]+l+" "+(k[1]+l),antialias:""+(r.string().indexOf(O)>-1),left:j[0]-j[2]*Number(f===G),top:j[1]-j[2]*Number(f===H),width:k[0]+l,height:k[1]+l}).each(function(a){var b=d(this);b[b.prop?"prop":"attr"]({coordsize:k[0]+l+" "+(k[1]+l),path:h,fillcolor:e[0],filled:!!a,stroked:!a}).toggle(!(!l&&!a)),!a&&b.html(vb("stroke",'weight="'+2*l+'px" color="'+e[1]+'" miterlimit="1000" joinstyle="miter"'))})),a.opera&&setTimeout(function(){m.tip.css({display:"inline-block",visibility:"visible"})},1),c!==E&&this.calculate(b,k)},calculate:function(a,b){if(!this.enabled)return E;var c,e,f=this,g=this.qtip.elements,h=this.element,i=this.options.offset,j=(g.tooltip.hasClass("ui-widget"),{});return a=a||this.corner,c=a.precedance,b=b||this._calculateSize(a),e=[a.x,a.y],c===G&&e.reverse(),d.each(e,function(d,e){var h,k,l; +e===O?(h=c===H?L:K,j[h]="50%",j[ib+"-"+h]=-Math.round(b[c===H?0:1]/2)+i):(h=f._parseWidth(a,e,g.tooltip),k=f._parseWidth(a,e,g.content),l=f._parseRadius(a),j[e]=Math.max(-f.border,d?k:i+(l>h?l:-h)))}),j[a[c]]-=b[c===G?0:1],h.css({margin:"",top:"",bottom:"",left:"",right:""}).css(j),j},reposition:function(a,b,d){function e(a,b,c,d,e){a===Q&&j.precedance===b&&k[d]&&j[c]!==O?j.precedance=j.precedance===G?H:G:a!==Q&&k[d]&&(j[b]=j[b]===O?k[d]>0?d:e:j[b]===d?e:d)}function f(a,b,e){j[a]===O?p[ib+"-"+b]=o[a]=g[ib+"-"+b]-k[b]:(h=g[e]!==c?[k[b],-g[b]]:[-k[b],g[b]],(o[a]=Math.max(h[0],h[1]))>h[0]&&(d[b]-=k[b],o[b]=E),p[g[e]!==c?e:b]=o[a])}if(this.enabled){var g,h,i=b.cache,j=this.corner.clone(),k=d.adjusted,l=b.options.position.adjust.method.split(" "),m=l[0],n=l[1]||l[0],o={left:E,top:E,x:0,y:0},p={};this.corner.fixed!==D&&(e(m,G,H,L,N),e(n,H,G,K,M),(j.string()!==i.corner.string()||i.cornerTop!==k.top||i.cornerLeft!==k.left)&&this.update(j,E)),g=this.calculate(j),g.right!==c&&(g.left=-g.right),g.bottom!==c&&(g.top=-g.bottom),g.user=this.offset,(o.left=m===Q&&!!k.left)&&f(G,L,N),(o.top=n===Q&&!!k.top)&&f(H,K,M),this.element.css(p).toggle(!(o.x&&o.y||j.x===O&&o.y||j.y===O&&o.x)),d.left-=g.left.charAt?g.user:m!==Q||o.top||!o.left&&!o.top?g.left+this.border:0,d.top-=g.top.charAt?g.user:n!==Q||o.left||!o.left&&!o.top?g.top+this.border:0,i.cornerLeft=k.left,i.cornerTop=k.top,i.corner=j.clone()}},destroy:function(){this.qtip._unbind(this.qtip.tooltip,this._ns),this.qtip.elements.tip&&this.qtip.elements.tip.find("*").remove().end().remove()}}),hb=R.tip=function(a){return new v(a,a.options.style.tip)},hb.initialize="render",hb.sanitize=function(a){if(a.style&&"tip"in a.style){var b=a.style.tip;"object"!=typeof b&&(b=a.style.tip={corner:b}),/string|boolean/i.test(typeof b.corner)||(b.corner=D)}},B.tip={"^position.my|style.tip.(corner|mimic|border)$":function(){this.create(),this.qtip.reposition()},"^style.tip.(height|width)$":function(a){this.size=[a.width,a.height],this.update(),this.qtip.reposition()},"^content.title|style.(classes|widget)$":function(){this.update()}},d.extend(D,y.defaults,{style:{tip:{corner:D,mimic:E,width:6,height:6,border:D,offset:0}}}),R.viewport=function(c,d,e,f,g,h,i){function j(a,b,c,e,f,g,h,i,j){var k=d[f],s=u[a],t=v[a],w=c===Q,x=s===f?j:s===g?-j:-j/2,y=t===f?i:t===g?-i:-i/2,z=q[f]+r[f]-(n?0:m[f]),A=z-k,B=k+j-(h===I?o:p)-z,C=x-(u.precedance===a||s===u[b]?y:0)-(t===O?i/2:0);return w?(C=(s===f?1:-1)*x,d[f]+=A>0?A:B>0?-B:0,d[f]=Math.max(-m[f]+r[f],k-C,Math.min(Math.max(-m[f]+r[f]+(h===I?o:p),k+C),d[f],"center"===s?k-x:1e9))):(e*=c===P?2:0,A>0&&(s!==f||B>0)?(d[f]-=C+e,l.invert(a,f)):B>0&&(s!==g||A>0)&&(d[f]-=(s===O?-C:C)+e,l.invert(a,g)),d[f]B&&(d[f]=k,l=u.clone())),d[f]-k}var k,l,m,n,o,p,q,r,s=e.target,t=c.elements.tooltip,u=e.my,v=e.at,w=e.adjust,x=w.method.split(" "),y=x[0],z=x[1]||x[0],A=e.viewport,B=e.container,C=(c.cache,{left:0,top:0});return A.jquery&&s[0]!==a&&s[0]!==b.body&&"none"!==w.method?(m=B.offset()||C,n="static"===B.css("position"),k="fixed"===t.css("position"),o=A[0]===a?A.width():A.outerWidth(E),p=A[0]===a?A.height():A.outerHeight(E),q={left:k?0:A.scrollLeft(),top:k?0:A.scrollTop()},r=A.offset()||C,("shift"!==y||"shift"!==z)&&(l=u.clone()),C={left:"none"!==y?j(G,H,y,w.x,L,N,I,f,h):0,top:"none"!==z?j(H,G,z,w.y,K,M,J,g,i):0,my:l}):C},R.polys={polygon:function(a,b){var c,d,e,f={width:0,height:0,position:{top:1e10,right:0,bottom:0,left:1e10},adjustable:E},g=0,h=[],i=1,j=1,k=0,l=0;for(g=a.length;g--;)c=[parseInt(a[--g],10),parseInt(a[g+1],10)],c[0]>f.position.right&&(f.position.right=c[0]),c[0]f.position.bottom&&(f.position.bottom=c[1]),c[1]0&&e>0&&i>0&&j>0;)for(d=Math.floor(d/2),e=Math.floor(e/2),b.x===L?i=d:b.x===N?i=f.width-d:i+=Math.floor(d/2),b.y===K?j=e:b.y===M?j=f.height-e:j+=Math.floor(e/2),g=h.length;g--&&!(h.length<2);)k=h[g][0]-f.position.left,l=h[g][1]-f.position.top,(b.x===L&&k>=i||b.x===N&&i>=k||b.x===O&&(i>k||k>f.width-i)||b.y===K&&l>=j||b.y===M&&j>=l||b.y===O&&(j>l||l>f.height-j))&&h.splice(g,1);f.position={left:h[0][0],top:h[0][1]}}return f},rect:function(a,b,c,d){return{width:Math.abs(c-a),height:Math.abs(d-b),position:{left:Math.min(a,c),top:Math.min(b,d)}}},_angles:{tc:1.5,tr:7/4,tl:5/4,bc:.5,br:.25,bl:.75,rc:2,lc:1,c:0},ellipse:function(a,b,c,d,e){var f=R.polys._angles[e.abbrev()],g=0===f?0:c*Math.cos(f*Math.PI),h=d*Math.sin(f*Math.PI);return{width:2*c-Math.abs(g),height:2*d-Math.abs(h),position:{left:a+g,top:b+h},adjustable:E}},circle:function(a,b,c,d){return R.polys.ellipse(a,b,c,c,d)}},R.imagemap=function(a,b,c){b.jquery||(b=d(b));var e,f,g,h,i,j=(b.attr("shape")||"rect").toLowerCase().replace("poly","polygon"),k=d('img[usemap="#'+b.parent("map").attr("name")+'"]'),l=d.trim(b.attr("coords")),m=l.replace(/,$/,"").split(",");if(!k.length)return E;if("polygon"===j)h=R.polys.polygon(m,c);else{if(!R.polys[j])return E;for(g=-1,i=m.length,f=[];++gparseInt(h[0].style.zIndex,10),b||e.closest(W)[0]===h[0]||c(e),g=a.target===k[k.length-1]}}var f,g,h,i,j=this,k={};d.extend(j,{init:function(){return i=j.elem=d("
    ",{id:"qtip-overlay",html:"
    ",mousedown:function(){return E}}).hide(),d(b.body).bind("focusin"+zb,e),d(b).bind("keydown"+zb,function(a){f&&f.options.show.modal.escape&&27===a.keyCode&&f.hide(a)}),i.bind("click"+zb,function(a){f&&f.options.show.modal.blur&&f.hide(a)}),j},update:function(b){f=b,k=b.options.show.modal.stealfocus!==E?b.tooltip.find("*").filter(function(){return a(this)}):[]},toggle:function(a,e,g){var k=(d(b.body),a.tooltip),l=a.options.show.modal,m=l.effect,n=e?"show":"hide",o=i.is(":visible"),p=d(zb).filter(":visible:not(:animated)").not(k);return j.update(a),e&&l.stealfocus!==E&&c(d(":focus")),i.toggleClass("blurs",l.blur),e&&i.appendTo(b.body),i.is(":animated")&&o===e&&h!==E||!e&&p.length?j:(i.stop(D,E),d.isFunction(m)?m.call(i,e):m===E?i[n]():i.fadeTo(parseInt(g,10)||90,e?1:0,function(){e||i.hide()}),e||i.queue(function(a){i.css({left:"",top:""}),d(zb).length||i.detach(),a()}),h=e,f.destroyed&&(f=F),j)}}),j.init()},xb=new xb,d.extend(w.prototype,{init:function(a){var b=a.tooltip;return this.options.on?(a.elements.overlay=xb.elem,b.addClass(yb).css("z-index",y.modal_zindex+d(zb).length),a._bind(b,["tooltipshow","tooltiphide"],function(a,c,e){var f=a.originalEvent;if(a.target===b[0])if(f&&"tooltiphide"===a.type&&/mouse(leave|enter)/.test(f.type)&&d(f.relatedTarget).closest(xb.elem[0]).length)try{a.preventDefault()}catch(g){}else(!f||f&&"tooltipsolo"!==f.type)&&this.toggle(a,"tooltipshow"===a.type,e)},this._ns,this),a._bind(b,"tooltipfocus",function(a,c){if(!a.isDefaultPrevented()&&a.target===b[0]){var e=d(zb),f=y.modal_zindex+e.length,g=parseInt(b[0].style.zIndex,10);xb.elem[0].style.zIndex=f-1,e.each(function(){this.style.zIndex>g&&(this.style.zIndex-=1)}),e.filter("."+$).qtip("blur",a.originalEvent),b.addClass($)[0].style.zIndex=f,xb.update(c);try{a.preventDefault()}catch(h){}}},this._ns,this),void a._bind(b,"tooltiphide",function(a){a.target===b[0]&&d(zb).filter(":visible").not(b).last().qtip("focus",a)},this._ns,this)):this},toggle:function(a,b,c){return a&&a.isDefaultPrevented()?this:void xb.toggle(this.qtip,!!b,c)},destroy:function(){this.qtip.tooltip.removeClass(yb),this.qtip._unbind(this.qtip.tooltip,this._ns),xb.toggle(this.qtip,E),delete this.qtip.elements.overlay}}),wb=R.modal=function(a){return new w(a,a.options.show.modal)},wb.sanitize=function(a){a.show&&("object"!=typeof a.show.modal?a.show.modal={on:!!a.show.modal}:"undefined"==typeof a.show.modal.on&&(a.show.modal.on=D))},y.modal_zindex=y.zindex-200,wb.initialize="render",B.modal={"^show.modal.(on|blur)$":function(){this.destroy(),this.init(),this.qtip.elems.overlay.toggle(this.qtip.tooltip[0].offsetWidth>0)}},d.extend(D,y.defaults,{show:{modal:{on:E,effect:D,blur:D,stealfocus:D,escape:D}}});var Ab,Bb='';d.extend(x.prototype,{_scroll:function(){var b=this.qtip.elements.overlay;b&&(b[0].style.top=d(a).scrollTop()+"px")},init:function(c){var e=c.tooltip;d("select, object").length<1&&(this.bgiframe=c.elements.bgiframe=d(Bb).appendTo(e),c._bind(e,"tooltipmove",this.adjustBGIFrame,this._ns,this)),this.redrawContainer=d("
    ",{id:S+"-rcontainer"}).appendTo(b.body),c.elements.overlay&&c.elements.overlay.addClass("qtipmodal-ie6fix")&&(c._bind(a,["scroll","resize"],this._scroll,this._ns,this),c._bind(e,["tooltipshow"],this._scroll,this._ns,this)),this.redraw()},adjustBGIFrame:function(){var a,b,c=this.qtip.tooltip,d={height:c.outerHeight(E),width:c.outerWidth(E)},e=this.qtip.plugins.tip,f=this.qtip.elements.tip;b=parseInt(c.css("borderLeftWidth"),10)||0,b={left:-b,top:-b},e&&f&&(a="x"===e.corner.precedance?[I,L]:[J,K],b[a[1]]-=f[a[0]]()),this.bgiframe.css(b).css(d)},redraw:function(){if(this.qtip.rendered<1||this.drawing)return this;var a,b,c,d,e=this.qtip.tooltip,f=this.qtip.options.style,g=this.qtip.options.position.container;return this.qtip.drawing=1,f.height&&e.css(J,f.height),f.width?e.css(I,f.width):(e.css(I,"").appendTo(this.redrawContainer),b=e.width(),1>b%2&&(b+=1),c=e.css("maxWidth")||"",d=e.css("minWidth")||"",a=(c+d).indexOf("%")>-1?g.width()/100:0,c=(c.indexOf("%")>-1?a:1)*parseInt(c,10)||b,d=(d.indexOf("%")>-1?a:1)*parseInt(d,10)||0,b=c+d?Math.min(Math.max(b,d),c):b,e.css(I,Math.round(b)).appendTo(g)),this.drawing=0,this},destroy:function(){this.bgiframe&&this.bgiframe.remove(),this.qtip._unbind([a,this.qtip.tooltip],this._ns)}}),Ab=R.ie6=function(a){return 6===db.ie?new x(a):E},Ab.initialize="render",B.ie6={"^content|style$":function(){this.redraw()}}})}(window,document); +//# sourceMappingURL=//cdn.jsdelivr.net/qtip2/2.2.1//var/www/qtip2/build/tmp/tmp-656464emu9s/jquery.qtip.min.map \ No newline at end of file diff --git a/gui/slick/js/lib/jquery.qtip-2012-04-26.min.js b/gui/slick/js/lib/jquery.qtip-2012-04-26.min.js deleted file mode 100644 index 0abe836c..00000000 --- a/gui/slick/js/lib/jquery.qtip-2012-04-26.min.js +++ /dev/null @@ -1,13 +0,0 @@ -/* -* qTip2 - Pretty powerful tooltips -* http://craigsworks.com/projects/qtip2/ -* -* Version: nightly -* Copyright 2009-2010 Craig Michael Thompson - http://craigsworks.com -* -* Dual licensed under MIT or GPLv2 licenses -* http://en.wikipedia.org/wiki/MIT_License -* http://en.wikipedia.org/wiki/GNU_General_Public_License -* -* Date: Thu Apr 26 12:17:04.0000000000 2012 -*//*jslint browser: true, onevar: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true *//*global window: false, jQuery: false, console: false, define: false */(function(a){typeof define==="function"&&define.amd?define(["jquery"],a):a(jQuery)})(function(a){function C(d){var e=this,f=d.options.show.modal,h=d.elements,i=h.tooltip,j="#qtip-overlay",k=".qtipmodal",l=k+d.id,n="is-modal-qtip",p=a(document.body),q;d.checks.modal={"^show.modal.(on|blur)$":function(){e.init(),h.overlay.toggle(i.is(":visible"))}},a.extend(e,{init:function(){if(!f.on)return e;q=e.create(),i.attr(n,b).css("z-index",g.modal.zindex+a(m+"["+n+"]").length).unbind(k).unbind(l).bind("tooltipshow"+k+" tooltiphide"+k,function(b,c,d){var f=b.originalEvent;if(b.target===i[0])if(f&&b.type==="tooltiphide"&&/mouse(leave|enter)/.test(f.type)&&a(f.relatedTarget).closest(q[0]).length)try{b.preventDefault()}catch(g){}else(!f||f&&!f.solo)&&e[b.type.replace("tooltip","")](b,d)}).bind("tooltipfocus"+k,function(b){if(!b.isDefaultPrevented()&&b.target===i[0]){var c=a(m).filter("["+n+"]"),d=g.modal.zindex+c.length,e=parseInt(i[0].style.zIndex,10);q[0].style.zIndex=d-1,c.each(function(){this.style.zIndex>e&&(this.style.zIndex-=1)}),c.end().filter("."+o).qtip("blur",b.originalEvent),i.addClass(o)[0].style.zIndex=d;try{b.preventDefault()}catch(f){}}}).bind("tooltiphide"+k,function(b){b.target===i[0]&&a("["+n+"]").filter(":visible").not(i).last().qtip("focus",b)}),f.escape&&a(window).unbind(l).bind("keydown"+l,function(a){a.keyCode===27&&i.hasClass(o)&&d.hide(a)}),f.blur&&h.overlay.unbind(l).bind("click"+l,function(a){i.hasClass(o)&&d.hide(a)});return e},create:function(){function d(){q.css({height:a(window).height(),width:a(window).width()})}var b=a(j);if(b.length)return h.overlay=b.insertAfter(a(m).last());q=h.overlay=a("
    ",{id:j.substr(1),html:"
    ",mousedown:function(){return c}}).insertAfter(a(m).last()),a(window).unbind(k).bind("resize"+k,d),d();return q},toggle:function(d,g,h){if(d&&d.isDefaultPrevented())return e;var j=f.effect,k=g?"show":"hide",o=q.is(":visible"),r=a("["+n+"]").filter(":visible").not(i),s;q||(q=e.create());if(q.is(":animated")&&o===g||!g&&r.length)return e;g?(q.css({left:0,top:0}),q.toggleClass("blurs",f.blur),p.bind("focusin"+l,function(b){var d=a(b.target),e=d.closest(".qtip"),f=e.length<1?c:parseInt(e[0].style.zIndex,10)>parseInt(i[0].style.zIndex,10);!f&&a(b.target).closest(m)[0]!==i[0]&&i.find("input:visible").filter(":first").focus()})):p.undelegate("*","focusin"+l),q.stop(b,c),a.isFunction(j)?j.call(q,g):j===c?q[k]():q.fadeTo(parseInt(h,10)||90,g?1:0,function(){g||a(this).hide()}),g||q.queue(function(a){q.css({left:"",top:""}),a()});return e},show:function(a,c){return e.toggle(a,b,c)},hide:function(a,b){return e.toggle(a,c,b)},destroy:function(){var b=q;b&&(b=a("["+n+"]").not(i).length<1,b?(h.overlay.remove(),a(window).unbind(k)):h.overlay.unbind(k+d.id),p.undelegate("*","focusin"+l));return i.removeAttr(n).unbind(k)}}),e.init()}function B(f,h){function y(a){var b=a.precedance==="y",c=n[b?"width":"height"],d=n[b?"height":"width"],e=a.string().indexOf("center")>-1,f=c*(e?.5:1),g=Math.pow,h=Math.round,i,j,k,l=Math.sqrt(g(f,2)+g(d,2)),m=[p/f*l,p/d*l];m[2]=Math.sqrt(g(m[0],2)-g(p,2)),m[3]=Math.sqrt(g(m[1],2)-g(p,2)),i=l+m[2]+m[3]+(e?0:m[0]),j=i/l,k=[h(j*d),h(j*c)];return{height:k[b?0:1],width:k[b?1:0]}}function x(b){var c=k.titlebar&&b.y==="top",d=c?k.titlebar:k.content,e=a.browser.mozilla,f=e?"-moz-":a.browser.webkit?"-webkit-":"",g=b.y+(e?"":"-")+b.x,h=f+(e?"border-radius-"+g:"border-"+g+"-radius");return parseInt(d.css(h),10)||parseInt(l.css(h),10)||0}function w(a,b,c){b=b?b:a[a.precedance];var d=l.hasClass(q),e=k.titlebar&&a.y==="top",f=e?k.titlebar:k.content,g="border-"+b+"-width",h;l.addClass(q),h=parseInt(f.css(g),10),h=(c?h||parseInt(l.css(g),10):h)||0,l.toggleClass(q,d);return h}function v(a,d,g,h){if(k.tip){var l=i.corner.clone(),n=g.adjusted,o=f.options.position.adjust.method.split(" "),p=o[0],q=o[1]||o[0],r={left:c,top:c,x:0,y:0},s,t={},u;i.corner.fixed!==b&&(p==="shift"&&l.precedance==="x"&&n.left&&l.y!=="center"?l.precedance=l.precedance==="x"?"y":"x":p!=="shift"&&n.left&&(l.x=l.x==="center"?n.left>0?"left":"right":l.x==="left"?"right":"left"),q==="shift"&&l.precedance==="y"&&n.top&&l.x!=="center"?l.precedance=l.precedance==="y"?"x":"y":q!=="shift"&&n.top&&(l.y=l.y==="center"?n.top>0?"top":"bottom":l.y==="top"?"bottom":"top"),l.string()!==m.corner.string()&&(m.top!==n.top||m.left!==n.left)&&i.update(l,c)),s=i.position(l,n),s.right!==e&&(s.left=-s.right),s.bottom!==e&&(s.top=-s.bottom),s.user=Math.max(0,j.offset);if(r.left=p==="shift"&&!!n.left)l.x==="center"?t["margin-left"]=r.x=s["margin-left"]-n.left:(u=s.right!==e?[n.left,-s.left]:[-n.left,s.left],(r.x=Math.max(u[0],u[1]))>u[0]&&(g.left-=n.left,r.left=c),t[s.right!==e?"right":"left"]=r.x);if(r.top=q==="shift"&&!!n.top)l.y==="center"?t["margin-top"]=r.y=s["margin-top"]-n.top:(u=s.bottom!==e?[n.top,-s.top]:[-n.top,s.top],(r.y=Math.max(u[0],u[1]))>u[0]&&(g.top-=n.top,r.top=c),t[s.bottom!==e?"bottom":"top"]=r.y);k.tip.css(t).toggle(!(r.x&&r.y||l.x==="center"&&r.y||l.y==="center"&&r.x)),g.left-=s.left.charAt?s.user:p!=="shift"||r.top||!r.left&&!r.top?s.left:0,g.top-=s.top.charAt?s.user:q!=="shift"||r.left||!r.left&&!r.top?s.top:0,m.left=n.left,m.top=n.top,m.corner=l.clone()}}function u(){n.width=j.width,n.height=j.height}function t(){var a=n.width;n.width=n.height,n.height=a}var i=this,j=f.options.style.tip,k=f.elements,l=k.tooltip,m={top:0,left:0},n={width:j.width,height:j.height},o={},p=j.border||0,r=".qtip-tip",s=!!(a("")[0]||{}).getContext;i.mimic=i.corner=d,i.border=p,i.offset=j.offset,i.size=n,f.checks.tip={"^position.my|style.tip.(corner|mimic|border)$":function(){i.init()||i.destroy(),f.reposition()},"^style.tip.(height|width)$":function(){n={width:j.width,height:j.height},i.create(),i.update(),f.reposition()},"^content.title.text|style.(classes|widget)$":function(){k.tip&&k.tip.length&&i.update()}},a.extend(i,{init:function(){var b=i.detectCorner()&&(s||a.browser.msie);b&&(i.create(),i.update(),l.unbind(r).bind("tooltipmove"+r,v));return b},detectCorner:function(){var a=j.corner,d=f.options.position,e=d.at,h=d.my.string?d.my.string():d.my;if(a===c||h===c&&e===c)return c;a===b?i.corner=new g.Corner(h):a.string||(i.corner=new g.Corner(a),i.corner.fixed=b),m.corner=new g.Corner(i.corner.string());return i.corner.string()!=="centercenter"},detectColours:function(b){var c,d,e,g=k.tip.css("cssText",""),h=b||i.corner,m=h[h.precedance],p="border-"+m+"-color",r="border"+m.charAt(0)+m.substr(1)+"Color",s=/rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i,t="background-color",u="transparent",v=" !important",w=a(document.body).css("color"),x=f.elements.content.css("color"),y=k.titlebar&&(h.y==="top"||h.y==="center"&&g.position().top+n.height/2+j.offset",{"class":"ui-tooltip-tip"}).css({width:b,height:c}).prependTo(l),s?a("").appendTo(k.tip)[0].getContext("2d").save():(d='',k.tip.html(d+d),a("*",k.tip).bind("click mousedown",function(a){a.stopPropagation()}))},update:function(e,f){var h=k.tip,l=h.children(),q=n.width,r=n.height,v="px solid ",x="px dashed transparent",z=j.mimic,B=Math.round,C,D,E,F,G;e||(e=m.corner||i.corner),z===c?z=e:(z=new g.Corner(z),z.precedance=e.precedance,z.x==="inherit"?z.x=e.x:z.y==="inherit"?z.y=e.y:z.x===z.y&&(z[e.precedance]=e[e.precedance])),C=z.precedance,e.precedance==="x"?t():u(),k.tip.css({width:q=n.width,height:r=n.height}),i.detectColours(e),o.border!=="transparent"&&o.border!=="#123456"?(p=w(e,d,b),j.border===0&&p>0&&(o.fill=o.border),i.border=p=j.border!==b?j.border:p):i.border=p=0,E=A(z,q,r),i.size=G=y(e),h.css(G),e.precedance==="y"?F=[B(z.x==="left"?p:z.x==="right"?G.width-q-p:(G.width-q)/2),B(z.y==="top"?G.height-r:0)]:F=[B(z.x==="left"?G.width-q:0),B(z.y==="top"?p:z.y==="bottom"?G.height-r-p:(G.height-r)/2)],s?(l.attr(G),D=l[0].getContext("2d"),D.restore(),D.save(),D.clearRect(0,0,3e3,3e3),D.translate(F[0],F[1]),D.beginPath(),D.moveTo(E[0][0],E[0][1]),D.lineTo(E[1][0],E[1][1]),D.lineTo(E[2][0],E[2][1]),D.closePath(),D.fillStyle=o.fill,D.strokeStyle=o.border,D.lineWidth=p*2,D.lineJoin="miter",D.miterLimit=100,p&&D.stroke(),D.fill()):(E="m"+E[0][0]+","+E[0][1]+" l"+E[1][0]+","+E[1][1]+" "+E[2][0]+","+E[2][1]+" xe",F[2]=p&&/^(r|b)/i.test(e.string())?parseFloat(a.browser.version,10)===8?2:1:0,l.css({antialias:""+(z.string().indexOf("center")>-1),left:F[0]-F[2]*Number(C==="x"),top:F[1]-F[2]*Number(C==="y"),width:q+p,height:r+p}).each(function(b){var c=a(this);c[c.prop?"prop":"attr"]({coordsize:q+p+" "+(r+p),path:E,fillcolor:o.fill,filled:!!b,stroked:!b}).css({display:p||b?"block":"none"}),!b&&c.html()===""&&c.html('')})),f!==c&&i.position(e)},position:function(d){var e=k.tip,f={},g=Math.max(0,j.offset),h,l,m;if(j.corner===c||!e)return c;d=d||i.corner,h=d.precedance,l=y(d),m=[d.x,d.y],h==="x"&&m.reverse(),a.each(m,function(a,c){var e,i;c==="center"?(e=h==="y"?"left":"top",f[e]="50%",f["margin-"+e]=-Math.round(l[h==="y"?"width":"height"]/2)+g):(e=w(d,c,b),i=x(d),f[c]=a?p?w(d,c):0:g+(i>e?i:0))}),f[d[h]]-=l[h==="x"?"width":"height"],e.css({top:"",bottom:"",left:"",right:"",margin:""}).css(f);return f},destroy:function(){k.tip&&k.tip.remove(),k.tip=!1,l.unbind(r)}}),i.init()}function A(a,b,c){var d=Math.ceil(b/2),e=Math.ceil(c/2),f={bottomright:[[0,0],[b,c],[b,0]],bottomleft:[[0,0],[b,0],[0,c]],topright:[[0,c],[b,0],[b,c]],topleft:[[0,0],[0,c],[b,c]],topcenter:[[0,c],[d,0],[b,c]],bottomcenter:[[0,0],[b,0],[d,c]],rightcenter:[[0,0],[b,e],[0,c]],leftcenter:[[b,0],[b,c],[0,e]]};f.lefttop=f.bottomright,f.righttop=f.bottomleft,f.leftbottom=f.topright,f.rightbottom=f.topleft;return f[a.string()]}function z(d){var e=this,g=d.elements.tooltip,h=d.options.content.ajax,i=f.defaults.content.ajax,j=".qtip-ajax",k=/)<[^<]*)*<\/script>/gi,l=b,m=c,n;d.checks.ajax={"^content.ajax":function(a,b,c){b==="ajax"&&(h=c),b==="once"?e.init():h&&h.url?e.load():g.unbind(j)}},a.extend(e,{init:function(){h&&h.url&&g.unbind(j)[h.once?"one":"bind"]("tooltipshow"+j,e.load);return e},load:function(f){function t(a,b,c){!d.destroyed&&a.status!==0&&d.set("content.text",b+": "+c)}function s(b,c,e){var f;d.destroyed||(o&&(b=a("
    ").append(b.replace(k,"")).find(o)),(f=i.success||h.success)&&a.isFunction(f)?f.call(h.context||d,b,c,e):d.set("content.text",b))}function r(){var e;d.destroyed||(l=c,p&&(m=b,d.show(f.originalEvent)),(e=i.complete||h.complete)&&a.isFunction(e)&&e.apply(h.context||d,arguments))}if(m)m=c;else{var g=h.url.indexOf(" "),j=h.url,o,p=!h.loading&&l;if(p)try{f.preventDefault()}catch(q){}else if(f&&f.isDefaultPrevented())return e;n&&n.abort&&n.abort(),g>-1&&(o=j.substr(g),j=j.substr(0,g)),n=a.ajax(a.extend({error:i.error||t,context:d},h,{url:j,success:s,complete:r}))}},destroy:function(){n&&n.abort&&n.abort(),d.destroyed=b}}),e.init()}function y(e,h){var i,j,k,l,m,n=a(this),o=a(document.body),p=this===document?o:n,q=n.metadata?n.metadata(h.metadata):d,r=h.metadata.type==="html5"&&q?q[h.metadata.name]:d,s=n.data(h.metadata.name||"qtipopts");try{s=typeof s==="string"?(new Function("return "+s))():s}catch(u){v("Unable to parse HTML5 attribute data: "+s)}l=a.extend(b,{},f.defaults,h,typeof s==="object"?w(s):d,w(r||q)),j=l.position,l.id=e;if("boolean"===typeof l.content.text){k=n.attr(l.content.attr);if(l.content.attr!==c&&k)l.content.text=k;else{v("Unable to locate content for tooltip! Aborting render of tooltip on element: ",n);return c}}j.container.length||(j.container=o),j.target===c&&(j.target=p),l.show.target===c&&(l.show.target=p),l.show.solo===b&&(l.show.solo=j.container.closest("body")),l.hide.target===c&&(l.hide.target=p),l.position.viewport===b&&(l.position.viewport=j.container),j.container=j.container.eq(0),j.at=new g.Corner(j.at),j.my=new g.Corner(j.my);if(a.data(this,"qtip"))if(l.overwrite)n.qtip("destroy");else if(l.overwrite===c)return c;l.suppress&&(m=a.attr(this,"title"))&&a(this).removeAttr("title").attr(t,m),i=new x(n,l,e,!!k),a.data(this,"qtip",i),n.bind("remove.qtip-"+e+" removeqtip.qtip-"+e,function(){i.destroy()});return i}function x(r,s,v,x){function Q(){var b=[s.show.target[0],s.hide.target[0],y.rendered&&F.tooltip[0],s.position.container[0],s.position.viewport[0],window,document];y.rendered?a([]).pushStack(a.grep(b,function(a){return typeof a==="object"})).unbind(E):s.show.target.unbind(E+"-create")}function P(){function o(a){y.rendered&&D[0].offsetWidth>0&&y.reposition(a)}function n(a){if(D.hasClass(l))return c;clearTimeout(y.timers.inactive),y.timers.inactive=setTimeout(function(){y.hide(a)},s.hide.inactive)}function k(b){if(D.hasClass(l)||B||C)return c;var f=a(b.relatedTarget||b.target),g=f.closest(m)[0]===D[0],h=f[0]===e.show[0];clearTimeout(y.timers.show),clearTimeout(y.timers.hide);if(d.target==="mouse"&&g||s.hide.fixed&&(/mouse(out|leave|move)/.test(b.type)&&(g||h)))try{b.preventDefault(),b.stopImmediatePropagation()}catch(i){}else s.hide.delay>0?y.timers.hide=setTimeout(function(){y.hide(b)},s.hide.delay):y.hide(b)}function j(a){if(D.hasClass(l))return c;clearTimeout(y.timers.show),clearTimeout(y.timers.hide);var d=function(){y.toggle(b,a)};s.show.delay>0?y.timers.show=setTimeout(d,s.show.delay):d()}var d=s.position,e={show:s.show.target,hide:s.hide.target,viewport:a(d.viewport),document:a(document),body:a(document.body),window:a(window)},g={show:a.trim(""+s.show.event).split(" "),hide:a.trim(""+s.hide.event).split(" ")},i=a.browser.msie&&parseInt(a.browser.version,10)===6;D.bind("mouseenter"+E+" mouseleave"+E,function(a){var b=a.type==="mouseenter";b&&y.focus(a),D.toggleClass(p,b)}),s.hide.fixed&&(e.hide=e.hide.add(D),D.bind("mouseover"+E,function(){D.hasClass(l)||clearTimeout(y.timers.hide)})),/mouse(out|leave)/i.test(s.hide.event)?s.hide.leave==="window"&&e.window.bind("mouseout"+E+" blur"+E,function(a){/select|option/.test(a.target)&&!a.relatedTarget&&y.hide(a)}):/mouse(over|enter)/i.test(s.show.event)&&e.hide.bind("mouseleave"+E,function(a){clearTimeout(y.timers.show)}),(""+s.hide.event).indexOf("unfocus")>-1&&d.container.closest("html").bind("mousedown"+E,function(b){var c=a(b.target),d=y.rendered&&!D.hasClass(l)&&D[0].offsetWidth>0,e=c.parents(m).filter(D[0]).length>0;c[0]!==r[0]&&c[0]!==D[0]&&!e&&!r.has(c[0]).length&&!c.attr("disabled")&&y.hide(b)}),"number"===typeof s.hide.inactive&&(e.show.bind("qtip-"+v+"-inactive",n),a.each(f.inactiveEvents,function(a,b){e.hide.add(F.tooltip).bind(b+E+"-inactive",n)})),a.each(g.hide,function(b,c){var d=a.inArray(c,g.show),f=a(e.hide);d>-1&&f.add(e.show).length===f.length||c==="unfocus"?(e.show.bind(c+E,function(a){D[0].offsetWidth>0?k(a):j(a)}),delete g.show[d]):e.hide.bind(c+E,k)}),a.each(g.show,function(a,b){e.show.bind(b+E,j)}),"number"===typeof s.hide.distance&&e.show.add(D).bind("mousemove"+E,function(a){var b=G.origin||{},c=s.hide.distance,d=Math.abs;(d(a.pageX-b.pageX)>=c||d(a.pageY-b.pageY)>=c)&&y.hide(a)}),d.target==="mouse"&&(e.show.bind("mousemove"+E,function(a){h={pageX:a.pageX,pageY:a.pageY,type:"mousemove"}}),d.adjust.mouse&&(s.hide.event&&(D.bind("mouseleave"+E,function(a){(a.relatedTarget||a.target)!==e.show[0]&&y.hide(a)}),F.target.bind("mouseenter"+E+" mouseleave"+E,function(a){G.onTarget=a.type==="mouseenter"})),e.document.bind("mousemove"+E,function(a){y.rendered&&G.onTarget&&!D.hasClass(l)&&D[0].offsetWidth>0&&y.reposition(a||h)}))),(d.adjust.resize||e.viewport.length)&&(a.event.special.resize?e.viewport:e.window).bind("resize"+E,o),(e.viewport.length||i&&D.css("position")==="fixed")&&e.viewport.bind("scroll"+E,o)}function O(b,d){function g(b){function i(e){e&&(delete h[e.src],clearTimeout(y.timers.img[e.src]),a(e).unbind(E)),a.isEmptyObject(h)&&(y.redraw(),d!==c&&y.reposition(G.event),b())}var g,h={};if((g=f.find("img[src]:not([height]):not([width])")).length===0)return i();g.each(function(b,c){if(h[c.src]===e){var d=0,f=3;(function g(){if(c.height||c.width||d>f)return i(c);d+=1,y.timers.img[c.src]=setTimeout(g,700)})(),a(c).bind("error"+E+" load"+E,function(){i(this)}),h[c.src]=c}})}var f=F.content;if(!y.rendered||!b)return c;a.isFunction(b)&&(b=b.call(r,G.event,y)||""),b.jquery&&b.length>0?f.empty().append(b.css({display:"block"})):f.html(b),y.rendered<0?D.queue("fx",g):(C=0,g(a.noop));return y}function N(b,d){var e=F.title;if(!y.rendered||!b)return c;a.isFunction(b)&&(b=b.call(r,G.event,y));if(b===c||!b&&b!=="")return J(c);b.jquery&&b.length>0?e.empty().append(b.css({display:"block"})):e.html(b),y.redraw(),d!==c&&y.rendered&&D[0].offsetWidth>0&&y.reposition(G.event)}function M(a){var b=F.button,d=F.title;if(!y.rendered)return c;a?(d||L(),K()):b.remove()}function L(){var c=A+"-title";F.titlebar&&J(),F.titlebar=a("
    ",{"class":j+"-titlebar "+(s.style.widget?"ui-widget-header":"")}).append(F.title=a("
    ",{id:c,"class":j+"-title","aria-atomic":b})).insertBefore(F.content).delegate(".ui-tooltip-close","mousedown keydown mouseup keyup mouseout",function(b){a(this).toggleClass("ui-state-active ui-state-focus",b.type.substr(-4)==="down")}).delegate(".ui-tooltip-close","mouseover mouseout",function(b){a(this).toggleClass("ui-state-hover",b.type==="mouseover")}),s.content.title.button?K():y.rendered&&y.redraw()}function K(){var b=s.content.title.button,d=typeof b==="string",e=d?b:"Close tooltip";F.button&&F.button.remove(),b.jquery?F.button=b:F.button=a("",{"class":"ui-state-default ui-tooltip-close "+(s.style.widget?"":j+"-icon"),title:e,"aria-label":e}).prepend(a("",{"class":"ui-icon ui-icon-close",html:"×"})),F.button.appendTo(F.titlebar).attr("role","button").click(function(a){D.hasClass(l)||y.hide(a);return c}),y.redraw()}function J(a){F.title&&(F.titlebar.remove(),F.titlebar=F.title=F.button=d,a!==c&&y.reposition())}function I(){var a=s.style.widget;D.toggleClass(k,a).toggleClass(n,s.style.def&&!a),F.content.toggleClass(k+"-content",a),F.titlebar&&F.titlebar.toggleClass(k+"-header",a),F.button&&F.button.toggleClass(j+"-icon",!a)}function H(a){var b=0,c,d=s,e=a.split(".");while(d=d[e[b++]])b0&&!a("#"+i).length&&(D[0].id=i,F.content[0].id=i+"-content",F.title[0].id=i+"-title")},"^content.text$":function(a,b,c){O(c)},"^content.title.text$":function(a,b,c){if(!c)return J();!F.title&&c&&L(),N(c)},"^content.title.button$":function(a,b,c){M(c)},"^position.(my|at)$":function(a,b,c){"string"===typeof c&&(a[b]=new g.Corner(c))},"^position.container$":function(a,b,c){y.rendered&&D.appendTo(c)},"^show.ready$":function(){y.rendered?y.toggle(b):y.render(1)},"^style.classes$":function(a,b,c){D.attr("class",j+" qtip ui-helper-reset "+c)},"^style.widget|content.title":I,"^events.(render|show|move|hide|focus|blur)$":function(b,c,d){D[(a.isFunction(d)?"":"un")+"bind"]("tooltip"+c,d)},"^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)":function(){var a=s.position;D.attr("tracking",a.target==="mouse"&&a.adjust.mouse),Q(),P()}},a.extend(y,{render:function(d){if(y.rendered)return y;var e=s.content.text,f=s.content.title.text,h=s.position,i=a.Event("tooltiprender");a.attr(r[0],"aria-describedby",A),D=F.tooltip=a("
    ",{id:A,"class":j+" qtip ui-helper-reset "+n+" "+s.style.classes+" "+j+"-pos-"+s.position.my.abbrev(),width:s.style.width||"",height:s.style.height||"",tracking:h.target==="mouse"&&h.adjust.mouse,role:"alert","aria-live":"polite","aria-atomic":c,"aria-describedby":A+"-content","aria-hidden":b}).toggleClass(l,G.disabled).data("qtip",y).appendTo(s.position.container).append(F.content=a("
    ",{"class":j+"-content",id:A+"-content","aria-atomic":b})),y.rendered=-1,B=C=1,f&&(L(),a.isFunction(f)||N(f,c)),a.isFunction(e)||O(e,c),y.rendered=b,I(),a.each(s.events,function(b,c){a.isFunction(c)&&D.bind(b==="toggle"?"tooltipshow tooltiphide":"tooltip"+b,c)}),a.each(g,function(){this.initialize==="render"&&this(y)}),P(),D.queue("fx",function(a){i.originalEvent=G.event,D.trigger(i,[y]),B=C=0,y.redraw(),(s.show.ready||d)&&y.toggle(b,G.event,c),a()});return y},get:function(a){var b,c;switch(a.toLowerCase()){case"dimensions":b={height:D.outerHeight(),width:D.outerWidth()};break;case"offset":b=g.offset(D,s.position.container);break;default:c=H(a.toLowerCase()),b=c[0][c[1]],b=b.precedance?b.string():b}return b},set:function(e,f){function m(a,b){var c,d,e;for(c in k)for(d in k[c])if(e=(new RegExp(d,"i")).exec(a))b.push(e),k[c][d].apply(y,b)}var g=/^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,h=/^content\.(title|attr)|style/i,i=c,j=c,k=y.checks,l;"string"===typeof e?(l=e,e={},e[l]=f):e=a.extend(b,{},e),a.each(e,function(b,c){var d=H(b.toLowerCase()),f;f=d[0][d[1]],d[0][d[1]]="object"===typeof c&&c.nodeType?a(c):c,e[b]=[d[0],d[1],c,f],i=g.test(b)||i,j=h.test(b)||j}),w(s),B=C=1,a.each(e,m),B=C=0,y.rendered&&D[0].offsetWidth>0&&(i&&y.reposition(s.position.target==="mouse"?d:G.event),j&&y.redraw());return y},toggle:function(e,f){function t(){e?(a.browser.msie&&D[0].style.removeAttribute("filter"),D.css("overflow",""),"string"===typeof i.autofocus&&a(i.autofocus,D).focus(),i.target.trigger("qtip-"+v+"-inactive")):D.css({display:"",visibility:"",opacity:"",left:"",top:""}),r=a.Event("tooltip"+(e?"visible":"hidden")),r.originalEvent=f?G.event:d,D.trigger(r,[y])}if(!y.rendered)return e?y.render(1):y;var g=e?"show":"hide",i=s[g],j=s[e?"hide":"show"],k=s.position,l=s.content,n=D[0].offsetWidth>0,o=e||i.target.length===1,p=!f||i.target.length<2||G.target[0]===f.target,q,r;(typeof e).search("boolean|number")&&(e=!n);if(!D.is(":animated")&&n===e&&p)return y;if(f){if(/over|enter/.test(f.type)&&/out|leave/.test(G.event.type)&&s.show.target.add(f.target).length===s.show.target.length&&D.has(f.relatedTarget).length)return y;G.event=a.extend({},f)}r=a.Event("tooltip"+g),r.originalEvent=f?G.event:d,D.trigger(r,[y,90]);if(r.isDefaultPrevented())return y;a.attr(D[0],"aria-hidden",!e),e?(G.origin=a.extend({},h),y.focus(f),a.isFunction(l.text)&&O(l.text,c),a.isFunction(l.title.text)&&N(l.title.text,c),!u&&k.target==="mouse"&&k.adjust.mouse&&(a(document).bind("mousemove.qtip",function(a){h={pageX:a.pageX,pageY:a.pageY,type:"mousemove"}}),u=b),y.reposition(f,arguments[2]),(r.solo=!!i.solo)&&a(m,i.solo).not(D).qtip("hide",r)):(clearTimeout(y.timers.show),delete G.origin,u&&!a(m+'[tracking="true"]:visible',i.solo).not(D).length&&(a(document).unbind("mousemove.qtip"),u=c),y.blur(f)),i.effect===c||o===c?(D[g](),t.call(D)):a.isFunction(i.effect)?(D.stop(1,1),i.effect.call(D,y),D.queue("fx",function(a){t(),a()})):D.fadeTo(90,e?1:0,t),e&&i.target.trigger("qtip-"+v+"-inactive");return y},show:function(a){return y.toggle(b,a)},hide:function(a){return y.toggle(c,a)},focus:function(b){if(!y.rendered)return y;var c=a(m),d=parseInt(D[0].style.zIndex,10),e=f.zindex+c.length,g=a.extend({},b),h,i;D.hasClass(o)||(i=a.Event("tooltipfocus"),i.originalEvent=g,D.trigger(i,[y,e]),i.isDefaultPrevented()||(d!==e&&(c.each(function(){this.style.zIndex>d&&(this.style.zIndex=this.style.zIndex-1)}),c.filter("."+o).qtip("blur",g)),D.addClass(o)[0].style.zIndex=e));return y},blur:function(b){var c=a.extend({},b),d;D.removeClass(o),d=a.Event("tooltipblur"),d.originalEvent=c,D.trigger(d,[y]);return y},reposition:function(b,d){if(!y.rendered||B)return y;B=1;var e=s.position.target,f=s.position,i=f.my,k=f.at,l=f.adjust,m=l.method.split(" "),n=D.outerWidth(),o=D.outerHeight(),p=0,q=0,r=a.Event("tooltipmove"),t=D.css("position")==="fixed",u=f.viewport,v={left:0,top:0},w=f.container,x=c,A=y.plugins.tip,C=D[0].offsetWidth>0,E={horizontal:m[0],vertical:m[1]=m[1]||m[0],enabled:u.jquery&&e[0]!==window&&e[0]!==z&&l.method!=="none",left:function(a){var b=E.horizontal==="shift",c=l.x*(E.horizontal.substr(-6)==="invert"?2:0),d=-w.offset.left+u.offset.left+u.scrollLeft,e=i.x==="left"?n:i.x==="right"?-n:-n/2,f=k.x==="left"?p:k.x==="right"?-p:-p/2,g=A&&A.size?A.size.width||0:0,h=A&&A.corner&&A.corner.precedance==="x"&&!b?g:0,j=d-a+h,m=a+n-u.width-d+h,o=e-(i.precedance==="x"||i.x===i.y?f:0)-(k.x==="center"?p/2:0),q=i.x==="center";b?(h=A&&A.corner&&A.corner.precedance==="y"?g:0,o=(i.x==="left"?1:-1)*e-h,v.left+=j>0?j:m>0?-m:0,v.left=Math.max(-w.offset.left+u.offset.left+(h&&A.corner.x==="center"?A.offset:0),a-o,Math.min(Math.max(-w.offset.left+u.offset.left+u.width,a+o),v.left))):(j>0&&(i.x!=="left"||m>0)?v.left-=o+c:m>0&&(i.x!=="right"||j>0)&&(v.left-=(q?-o:o)+c),v.leftm&&(v.left=a));return v.left-a},top:function(a){var b=E.vertical==="shift",c=l.y*(E.vertical.substr(-6)==="invert"?2:0),d=-w.offset.top+u.offset.top+u.scrollTop,e=i.y==="top"?o:i.y==="bottom"?-o:-o/2,f=k.y==="top"?q:k.y==="bottom"?-q:-q/2,g=A&&A.size?A.size.height||0:0,h=A&&A.corner&&A.corner.precedance==="y"&&!b?g:0,j=d-a+h,m=a+o-u.height-d+h,n=e-(i.precedance==="y"||i.x===i.y?f:0)-(k.y==="center"?q/2:0),p=i.y==="center";b?(h=A&&A.corner&&A.corner.precedance==="x"?g:0,n=(i.y==="top"?1:-1)*e-h,v.top+=j>0?j:m>0?-m:0,v.top=Math.max(-w.offset.top+u.offset.top+(h&&A.corner.x==="center"?A.offset:0),a-n,Math.min(Math.max(-w.offset.top+u.offset.top+u.height,a+n),v.top))):(j>0&&(i.y!=="top"||m>0)?v.top-=n+c:m>0&&(i.y!=="bottom"||j>0)&&(v.top-=(p?-n:n)+c),v.top<0&&-v.top>m&&(v.top=a));return v.top-a}},H;if(a.isArray(e)&&e.length===2)k={x:"left",y:"top"},v={left:e[0],top:e[1]};else if(e==="mouse"&&(b&&b.pageX||G.event.pageX))k={x:"left",y:"top"},b=(b&&(b.type==="resize"||b.type==="scroll")?G.event:b&&b.pageX&&b.type==="mousemove"?b:h&&h.pageX&&(l.mouse||!b||!b.pageX)?{pageX:h.pageX,pageY:h.pageY}:!l.mouse&&G.origin&&G.origin.pageX&&s.show.distance?G.origin:b)||b||G.event||h||{},v={top:b.pageY,left:b.pageX};else{e==="event"?b&&b.target&&b.type!=="scroll"&&b.type!=="resize"?e=G.target=a(b.target):e=G.target:e=G.target=a(e.jquery?e:F.target),e=a(e).eq(0);if(e.length===0)return y;e[0]===document||e[0]===window?(p=g.iOS?window.innerWidth:e.width(),q=g.iOS?window.innerHeight:e.height(),e[0]===window&&(v={top:(u||e).scrollTop(),left:(u||e).scrollLeft()})):e.is("area")&&g.imagemap?v=g.imagemap(e,k,E.enabled?m:c):e[0].namespaceURI==="http://www.w3.org/2000/svg"&&g.svg?v=g.svg(e,k):(p=e.outerWidth(),q=e.outerHeight(),v=g.offset(e,w)),v.offset&&(p=v.width,q=v.height,x=v.flipoffset,v=v.offset);if(g.iOS<4.1&&g.iOS>3.1||g.iOS==4.3||!g.iOS&&t)H=a(window),v.left-=H.scrollLeft(),v.top-=H.scrollTop();v.left+=k.x==="right"?p:k.x==="center"?p/2:0,v.top+=k.y==="bottom"?q:k.y==="center"?q/2:0}v.left+=l.x+(i.x==="right"?-n:i.x==="center"?-n/2:0),v.top+=l.y+(i.y==="bottom"?-o:i.y==="center"?-o/2:0),E.enabled?(u={elem:u,height:u[(u[0]===window?"h":"outerH")+"eight"](),width:u[(u[0]===window?"w":"outerW")+"idth"](),scrollLeft:t?0:u.scrollLeft(),scrollTop:t?0:u.scrollTop(),offset:u.offset()||{left:0,top:0}},w={elem:w,scrollLeft:w.scrollLeft(),scrollTop:w.scrollTop(),offset:w.offset()||{left:0,top:0}},v.adjusted={left:E.horizontal!=="none"?E.left(v.left):0,top:E.vertical!=="none"?E.top(v.top):0},v.adjusted.left+v.adjusted.top&&D.attr("class",D[0].className.replace(/ui-tooltip-pos-\w+/i,j+"-pos-"+i.abbrev())),x&&v.adjusted.left&&(v.left+=x.left),x&&v.adjusted.top&&(v.top+=x.top)):v.adjusted={left:0,top:0},r.originalEvent=a.extend({},b),D.trigger(r,[y,v,u.elem||u]);if(r.isDefaultPrevented())return y;delete v.adjusted,d===c||!C||isNaN(v.left)||isNaN(v.top)||e==="mouse"||!a.isFunction(f.effect)?D.css(v):a.isFunction(f.effect)&&(f.effect.call(D,y,a.extend({},v)),D.queue(function(b){a(this).css({opacity:"",height:""}),a.browser.msie&&this.style.removeAttribute("filter"),b()})),B=0;return y},redraw:function(){if(y.rendered<1||C)return y;var a=s.position.container,b,c,d,e;C=1,s.style.height&&D.css("height",s.style.height),s.style.width?D.css("width",s.style.width):(D.css("width","").addClass(q),c=D.width()+1,d=D.css("max-width")||"",e=D.css("min-width")||"",b=(d+e).indexOf("%")>-1?a.width()/100:0,d=(d.indexOf("%")>-1?b:1)*parseInt(d,10)||c,e=(e.indexOf("%")>-1?b:1)*parseInt(e,10)||0,c=d+e?Math.min(Math.max(c,e),d):c,D.css("width",Math.round(c)).removeClass(q)),C=0;return y},disable:function(b){"boolean"!==typeof b&&(b=!D.hasClass(l)&&!G.disabled),y.rendered?(D.toggleClass(l,b),a.attr(D[0],"aria-disabled",b)):G.disabled=!!b;return y},enable:function(){return y.disable(c)},destroy:function(){var c=r[0],d=a.attr(c,t),e=r.data("qtip");y.destroyed=b,y.rendered&&(D.stop(1,0).remove(),a.each(y.plugins,function(){this.destroy&&this.destroy()})),clearTimeout(y.timers.show),clearTimeout(y.timers.hide),Q();if(!e||y===e)a.removeData(c,"qtip"),s.suppress&&d&&(a.attr(c,"title",d),r.removeAttr(t)),r.removeAttr("aria-describedby");r.unbind(".qtip-"+v),delete i[y.id];return r}})}function w(b){var e;if(!b||"object"!==typeof b)return c;if(b.metadata===d||"object"!==typeof b.metadata)b.metadata={type:b.metadata};if("content"in b){if(b.content===d||"object"!==typeof b.content||b.content.jquery)b.content={text:b.content};e=b.content.text||c,!a.isFunction(e)&&(!e&&!e.attr||e.length<1||"object"===typeof e&&!e.jquery)&&(b.content.text=c);if("title"in b.content){if(b.content.title===d||"object"!==typeof b.content.title)b.content.title={text:b.content.title};e=b.content.title.text||c,!a.isFunction(e)&&(!e&&!e.attr||e.length<1||"object"===typeof e&&!e.jquery)&&(b.content.title.text=c)}}if("position"in b)if(b.position===d||"object"!==typeof b.position)b.position={my:b.position,at:b.position};if("show"in b)if(b.show===d||"object"!==typeof b.show)b.show.jquery?b.show={target:b.show}:b.show={event:b.show};if("hide"in b)if(b.hide===d||"object"!==typeof b.hide)b.hide.jquery?b.hide={target:b.hide}:b.hide={event:b.hide};if("style"in b)if(b.style===d||"object"!==typeof b.style)b.style={classes:b.style};a.each(g,function(){this.sanitize&&this.sanitize(b)});return b}function v(){v.history=v.history||[],v.history.push(arguments);if("object"===typeof console){var a=console[console.warn?"warn":"log"],b=Array.prototype.slice.call(arguments),c;typeof arguments[0]==="string"&&(b[0]="qTip2: "+b[0]),c=a.apply?a.apply(console,b):a(b)}}"use strict";var b=!0,c=!1,d=null,e,f,g,h,i={},j="ui-tooltip",k="ui-widget",l="ui-state-disabled",m="div.qtip."+j,n=j+"-default",o=j+"-focus",p=j+"-hover",q=j+"-fluid",r="-31000px",s="_replacedByqTip",t="oldtitle",u;f=a.fn.qtip=function(g,h,i){var j=(""+g).toLowerCase(),k=d,l=a.makeArray(arguments).slice(1),m=l[l.length-1],n=this[0]?a.data(this[0],"qtip"):d;if(!arguments.length&&n||j==="api")return n;if("string"===typeof g){this.each(function(){var d=a.data(this,"qtip");if(!d)return b;m&&m.timeStamp&&(d.cache.event=m);if(j!=="option"&&j!=="options"||!h)d[j]&&d[j].apply(d[j],l);else if(a.isPlainObject(h)||i!==e)d.set(h,i);else{k=d.get(h);return c}});return k!==d?k:this}if("object"===typeof g||!arguments.length){n=w(a.extend(b,{},g));return f.bind.call(this,n,m)}},f.bind=function(d,j){return this.each(function(k){function r(b){function d(){p.render(typeof b==="object"||l.show.ready),m.show.add(m.hide).unbind(o)}if(p.cache.disabled)return c;p.cache.event=a.extend({},b),p.cache.target=b?a(b.target):[e],l.show.delay>0?(clearTimeout(p.timers.show),p.timers.show=setTimeout(d,l.show.delay),n.show!==n.hide&&m.hide.bind(n.hide,function(){clearTimeout(p.timers.show)})):d()}var l,m,n,o,p,q;q=a.isArray(d.id)?d.id[k]:d.id,q=!q||q===c||q.length<1||i[q]?f.nextid++:i[q]=q,o=".qtip-"+q+"-create",p=y.call(this,q,d);if(p===c)return b;l=p.options,a.each(g,function(){this.initialize==="initialize"&&this(p)}),m={show:l.show.target,hide:l.hide.target},n={show:a.trim(""+l.show.event).replace(/ /g,o+" ")+o,hide:a.trim(""+l.hide.event).replace(/ /g,o+" ")+o},/mouse(over|enter)/i.test(n.show)&&!/mouse(out|leave)/i.test(n.hide)&&(n.hide+=" mouseleave"+o),m.show.bind("mousemove"+o,function(a){h={pageX:a.pageX,pageY:a.pageY,type:"mousemove"},p.cache.onTarget=b}),m.show.bind(n.show,r),(l.show.ready||l.prerender)&&r(j)})},g=f.plugins={Corner:function(a){a=(""+a).replace(/([A-Z])/," $1").replace(/middle/gi,"center").toLowerCase(),this.x=(a.match(/left|right/i)||a.match(/center/)||["inherit"])[0].toLowerCase(),this.y=(a.match(/top|bottom|center/i)||["inherit"])[0].toLowerCase();var b=a.charAt(0);this.precedance=b==="t"||b==="b"?"y":"x",this.string=function(){return this.precedance==="y"?this.y+this.x:this.x+this.y},this.abbrev=function(){var a=this.x.substr(0,1),b=this.y.substr(0,1);return a===b?a:a==="c"||a!=="c"&&b!=="c"?b+a:a+b},this.clone=function(){return{x:this.x,y:this.y,precedance:this.precedance,string:this.string,abbrev:this.abbrev,clone:this.clone}}},offset:function(b,c){function j(a,b){d.left+=b*a.scrollLeft(),d.top+=b*a.scrollTop()}var d=b.offset(),e=b.closest("body")[0],f=c,g,h,i;if(f){do f.css("position")!=="static"&&(h=f.position(),d.left-=h.left+(parseInt(f.css("borderLeftWidth"),10)||0)+(parseInt(f.css("marginLeft"),10)||0),d.top-=h.top+(parseInt(f.css("borderTopWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0),!g&&(i=f.css("overflow"))!=="hidden"&&i!=="visible"&&(g=f));while((f=a(f[0].offsetParent)).length);g&&g[0]!==e&&j(g,1)}return d},iOS:parseFloat((""+(/CPU.*OS ([0-9_]{1,3})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_","."))||c,fn:{attr:function(b,c){if(this.length){var d=this[0],e="title",f=a.data(d,"qtip");if(b===e&&f&&"object"===typeof f&&f.options.suppress){if(arguments.length<2)return a.attr(d,t);f&&f.options.content.attr===e&&f.cache.attr&&f.set("content.text",c);return this.attr(t,c)}}return a.fn["attr"+s].apply(this,arguments)},clone:function(b){var c=a([]),d="title",e=a.fn["clone"+s].apply(this,arguments);b||e.filter("["+t+"]").attr("title",function(){return a.attr(this,t)}).removeAttr(t);return e}}},a.each(g.fn,function(c,d){if(!d||a.fn[c+s])return b;var e=a.fn[c+s]=a.fn[c];a.fn[c]=function(){return d.apply(this,arguments)||e.apply(this,arguments)}}),a.ui||(a["cleanData"+s]=a.cleanData,a.cleanData=function(b){for(var c=0,d;(d=b[c])!==e;c++)try{a(d).triggerHandler("removeqtip")}catch(f){}a["cleanData"+s](b)}),f.version="nightly",f.nextid=0,f.inactiveEvents="click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(" "),f.zindex=15e3,f.defaults={prerender:c,id:c,overwrite:b,suppress:b,content:{text:b,attr:"title",title:{text:c,button:c}},position:{my:"top left",at:"bottom right",target:c,container:c,viewport:c,adjust:{x:0,y:0,mouse:b,resize:b,method:"flip flip"},effect:function(b,d,e){a(this).animate(d,{duration:200,queue:c})}},show:{target:c,event:"mouseenter",effect:b,delay:90,solo:c,ready:c,autofocus:c},hide:{target:c,event:"mouseleave",effect:b,delay:0,fixed:c,inactive:c,leave:"window",distance:c},style:{classes:"",widget:c,width:c,height:c,def:b},events:{render:d,move:d,show:d,hide:d,toggle:d,visible:d,hidden:d,focus:d,blur:d}},g.ajax=function(a){var b=a.plugins.ajax;return"object"===typeof b?b:a.plugins.ajax=new z(a)},g.ajax.initialize="render",g.ajax.sanitize=function(a){var b=a.content,c;b&&"ajax"in b&&(c=b.ajax,typeof c!=="object"&&(c=a.content.ajax={url:c}),"boolean"!==typeof c.once&&c.once&&(c.once=!!c.once))},a.extend(b,f.defaults,{content:{ajax:{loading:b,once:b}}}),g.tip=function(a){var b=a.plugins.tip;return"object"===typeof b?b:a.plugins.tip=new B(a)},g.tip.initialize="render",g.tip.sanitize=function(a){var c=a.style,d;c&&"tip"in c&&(d=a.style.tip,typeof d!=="object"&&(a.style.tip={corner:d}),/string|boolean/i.test(typeof d.corner)||(d.corner=b),typeof d.width!=="number"&&delete d.width,typeof d.height!=="number"&&delete d.height,typeof d.border!=="number"&&d.border!==b&&delete d.border,typeof d.offset!=="number"&&delete d.offset)},a.extend(b,f.defaults,{style:{tip:{corner:b,mimic:c,width:6,height:6,border:b,offset:0}}}),g.modal=function(a){var b=a.plugins.modal;return"object"===typeof b?b:a.plugins.modal=new C(a)},g.modal.initialize="render",g.modal.sanitize=function(a){a.show&&(typeof a.show.modal!=="object"?a.show.modal={on:!!a.show.modal}:typeof a.show.modal.on==="undefined"&&(a.show.modal.on=b))},g.modal.zindex=f.zindex+1e3,a.extend(b,f.defaults,{show:{modal:{on:c,effect:b,blur:b,escape:b}}})}) \ No newline at end of file diff --git a/gui/slick/js/manageEpisodeStatuses.js b/gui/slick/js/manageEpisodeStatuses.js index cc97068b..b9354e1a 100644 --- a/gui/slick/js/manageEpisodeStatuses.js +++ b/gui/slick/js/manageEpisodeStatuses.js @@ -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 += '
    '; - row += ' '; - row += ' '; - row += ' '; - row += ' ' - - return row; + var checkedbox = (checked ? ' checked' : ''), + row_class = $('#row_class').val(); + + return ' ' + + ' ' + + ' ' + + ' ' + + ' '; } + $('.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() diff --git a/gui/slick/js/newShow.js b/gui/slick/js/newShow.js index 6018f360..8ec6157b 100644 --- a/gui/slick/js/newShow.js +++ b/gui/slick/js/newShow.js @@ -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 = ''; - } else { - $.each(data.results, function (index, obj) { - if (resultStr == '') { - selected = ' selected="selected"'; - } else { - selected = ''; - } + $.getJSON(sbRoot + '/home/addShows/getIndexerLanguages', {}, function (data) { - resultStr += ''; - }); - } + var resultStr = '', + selected = ' selected="selected"', + elIndexerLang = $('#indexerLangSelect'); - $('#indexerLangSelect').html(resultStr); - $('#indexerLangSelect').change(function () { searchIndexers(); }); - }); - } - } + if (0 === data.results.length) { + resultStr = ''; + } else { + $.each(data.results, function (index, obj) { + resultStr += ''; + }); + } - 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(' 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 = '
    \nSearch Results:\n'; - var checked = ''; + if (searchRequestXhr) + searchRequestXhr.abort(); - if (data.results.length === 0) { - resultStr += 'No results found, try a different search.'; - } 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(' 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 += ' '; - if (data.langid && data.langid != "") { - resultStr += '' + obj[4] + ''; - } else { - resultStr += '' + obj[4] + ''; - } + if (0 === data.results.length) { + resultStr += 'Sorry, no results found. Try a different search.'; + } 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 = ' (' + + (startDate > today ? 'will debut' : 'started') + + ' on ' + obj[5] + ')'; + } - resultStr += '
    '; - }); - resultStr += ''; - } - resultStr += '
    '; - $('#searchResults').html(resultStr); - updateSampleText(); - myform.loadsection(0); - } - }); - } + resultStr += '' + + '' + + '' + obj[4] + '' + + showstartdate + + (null == obj[0] ? '' + : ' ' + '[' + obj[0] + ']' + '') + + '' + "\n"; + }); + } + $('#searchResults').html( + '
    ' + "\n" + '' + + (0 < row ? row : 'No') + + ' search result' + (1 == row ? '' : 's') + '...' + "\n" + + resultStr + + '
    ' + ); + 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 ' + show_name + ' into '; + // 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 = '

    Adding show ' + show_name + '' + + ('' == show_name ? 'into
    ' : '
    into') + + ' '; - if (root_dir_text.substr(sample_text.length - 1) != sep_char) { - root_dir_text += sep_char; - } - root_dir_text += '||' + 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 += '||' + sep_char; - sample_text += '
    '; + 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 += '

    '; - // 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' + } + }); + }); }); diff --git a/gui/slick/js/plotTooltip.js b/gui/slick/js/plotTooltip.js index 6f737922..b15009d3 100644 --- a/gui/slick/js/plotTooltip.js +++ b/gui/slick/js/plotTooltip.js @@ -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' } }); }); diff --git a/gui/slick/js/qualityChooser.js b/gui/slick/js/qualityChooser.js index 1fb8b498..866f2d1b 100644 --- a/gui/slick/js/qualityChooser.js +++ b/gui/slick/js/qualityChooser.js @@ -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()); +}); \ No newline at end of file diff --git a/gui/slick/js/ratingTooltip.js b/gui/slick/js/ratingTooltip.js index 712c2f9a..35a481b7 100644 --- a/gui/slick/js/ratingTooltip.js +++ b/gui/slick/js/ratingTooltip.js @@ -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' } }); }); diff --git a/gui/slick/js/recommendedShows.js b/gui/slick/js/recommendedShows.js index f2b9bd1f..4f7dee00 100644 --- a/gui/slick/js/recommendedShows.js +++ b/gui/slick/js/recommendedShows.js @@ -1,144 +1,211 @@ -$(document).ready(function () { - function getRecommendedShows() { - $.getJSON(sbRoot + '/home/addShows/getRecommendedShows', {}, function (data) { - var firstResult = true; - var resultStr = '
    \nRecommended Shows:\n'; - var checked = ''; +$(document).ready(function (){ - if (data.results.length === 0) { - resultStr += 'No recommended shows found, update your watched shows list on trakt.tv.'; - } else { - $.each(data.results, function (index, obj) { - if (firstResult) { - checked = ' checked'; - firstResult = false; - } else { - checked = ''; - } + function getRecommendedShows(){ - var whichSeries = obj.join('|'); + $('#searchResults').empty().html('' + + ' fetching recommendations...'); - resultStr += ' '; - resultStr += '' + obj[2] + ''; + $.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 += '

    Sorry, no recommended shows found, this can happen from time to time.

    ' + + '

    However, if the issue persists, then try updating your watched shows list on trakt.tv

    '; + } 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 += '
    ' + obj[3]; - } + var whichSeries = obj[6] + '|' + obj[0] + '|' + obj[1] + '|' + obj[2] + '|' + obj[3], + showstartdate = ''; - resultStr += '


    '; - }); - resultStr += ''; - } - resultStr += '

    '; - $('#searchResults').html(resultStr); - updateSampleText(); - myform.loadsection(0); - }); - } + if (null !== obj[3]){ + var startDate = new Date(obj[3]); + var today = new Date(); + showstartdate = ' (' + + (startDate > today ? 'will debut' : 'started') + + ' on ' + obj[3] + ')'; + } - $('#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 += '' + + '' + + '
    ' + + '' + obj[1] + '' + + showstartdate + + (null == obj[6] ? '' + : ' ' + + '' + + '' + + '' + obj[4] + '' + + '' + + '' + + '' + ) + + (null == obj[10] ? '' + : ' ' + + '' + + '' + + '' + obj[8] + '' + + '' + + '' + + '' + ) + + (null == obj[2] ? '' + : ' 
    ' + obj[2] + '
    ') + + '
    '; + }); + } - $('#recommendedShowsForm').submit(); - }); + $('#searchResults').html( + '
    ' + "\n" + '' + + (0 < row ? row : 'No') + + ' recommended result' + (1 == row ? '' : 's') + '...' + "\n" + + resultStr + + '
    ' + ); + 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 ' + show_name + ' into '; + 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 = '

    Adding show ' + show_name + '' + + ('' == show_name ? 'into
    ' : '
    into') + + ' '; - // 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 += '||' + sep_char; + root_dir_text += (sep_char != root_dir_text.substr(sample_text.length - 1) + ? sep_char : '') + + '||' + 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 += '
    '; + sample_text += '

    '; - // 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); }); diff --git a/gui/slick/js/sceneExceptionsTooltip.js b/gui/slick/js/sceneExceptionsTooltip.js index 46e525a3..ae12a0db 100644 --- a/gui/slick/js/sceneExceptionsTooltip.js +++ b/gui/slick/js/sceneExceptionsTooltip.js @@ -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' } }); }); diff --git a/setup.py b/setup.py deleted file mode 100644 index 7010fbef..00000000 --- a/setup.py +++ /dev/null @@ -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() diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 75170f5c..8cbddb7f 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -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) diff --git a/sickbeard/blackandwhitelist.py b/sickbeard/blackandwhitelist.py index 70b35cd0..40414b73 100644 --- a/sickbeard/blackandwhitelist.py +++ b/sickbeard/blackandwhitelist.py @@ -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 ([], {}) diff --git a/sickbeard/databases/mainDB.py b/sickbeard/databases/mainDB.py index be30ffa8..12a1c8ae 100644 --- a/sickbeard/databases/mainDB.py +++ b/sickbeard/databases/mainDB.py @@ -27,7 +27,8 @@ from sickbeard import encodingKludge as ek from sickbeard.name_parser.parser import NameParser, InvalidNameException, InvalidShowException MIN_DB_VERSION = 9 # oldest db version we support migrating from -MAX_DB_VERSION = 40 +MAX_DB_VERSION = 20000 + class MainSanityCheck(db.DBSanityCheck): def check(self): @@ -39,143 +40,145 @@ class MainSanityCheck(db.DBSanityCheck): def fix_duplicate_shows(self, column='indexer_id'): - sqlResults = self.connection.select( - "SELECT show_id, " + column + ", COUNT(" + column + ") as count FROM tv_shows GROUP BY " + column + " HAVING count > 1") + sql_results = self.connection.select( + 'SELECT show_id, ' + column + ', COUNT(' + column + ') as count FROM tv_shows GROUP BY ' + column + ' HAVING count > 1') - for cur_duplicate in sqlResults: + for cur_duplicate in sql_results: - logger.log(u"Duplicate show detected! " + column + ": " + str(cur_duplicate[column]) + u" count: " + str( - cur_duplicate["count"]), logger.DEBUG) + logger.log(u'Duplicate show detected! ' + column + ': ' + str(cur_duplicate[column]) + u' count: ' + str( + cur_duplicate['count']), logger.DEBUG) cur_dupe_results = self.connection.select( - "SELECT show_id, " + column + " FROM tv_shows WHERE " + column + " = ? LIMIT ?", - [cur_duplicate[column], int(cur_duplicate["count"]) - 1] + 'SELECT show_id, ' + column + ' FROM tv_shows WHERE ' + column + ' = ? LIMIT ?', + [cur_duplicate[column], int(cur_duplicate['count']) - 1] ) for cur_dupe_id in cur_dupe_results: logger.log( - u"Deleting duplicate show with " + column + ": " + str(cur_dupe_id[column]) + u" show_id: " + str( - cur_dupe_id["show_id"])) - self.connection.action("DELETE FROM tv_shows WHERE show_id = ?", [cur_dupe_id["show_id"]]) + u'Deleting duplicate show with ' + column + ': ' + str(cur_dupe_id[column]) + u' show_id: ' + str( + cur_dupe_id['show_id'])) + self.connection.action('DELETE FROM tv_shows WHERE show_id = ?', [cur_dupe_id['show_id']]) else: - logger.log(u"No duplicate show, check passed") + logger.log(u'No duplicate show, check passed') def fix_duplicate_episodes(self): - sqlResults = self.connection.select( - "SELECT showid, season, episode, COUNT(showid) as count FROM tv_episodes GROUP BY showid, season, episode HAVING count > 1") + sql_results = self.connection.select( + 'SELECT showid, season, episode, COUNT(showid) as count FROM tv_episodes GROUP BY showid, season, episode HAVING count > 1') - for cur_duplicate in sqlResults: + for cur_duplicate in sql_results: - logger.log(u"Duplicate episode detected! showid: " + str(cur_duplicate["showid"]) + u" season: " + str( - cur_duplicate["season"]) + u" episode: " + str(cur_duplicate["episode"]) + u" count: " + str( - cur_duplicate["count"]), logger.DEBUG) + logger.log(u'Duplicate episode detected! showid: ' + str(cur_duplicate['showid']) + u' season: ' + + str(cur_duplicate['season']) + u' episode: ' + str(cur_duplicate['episode']) + u' count: ' + + str(cur_duplicate['count']), + logger.DEBUG) cur_dupe_results = self.connection.select( - "SELECT episode_id FROM tv_episodes WHERE showid = ? AND season = ? and episode = ? ORDER BY episode_id DESC LIMIT ?", - [cur_duplicate["showid"], cur_duplicate["season"], cur_duplicate["episode"], - int(cur_duplicate["count"]) - 1] + 'SELECT episode_id FROM tv_episodes WHERE showid = ? AND season = ? and episode = ? ORDER BY episode_id DESC LIMIT ?', + [cur_duplicate['showid'], cur_duplicate['season'], cur_duplicate['episode'], + int(cur_duplicate['count']) - 1] ) for cur_dupe_id in cur_dupe_results: - logger.log(u"Deleting duplicate episode with episode_id: " + str(cur_dupe_id["episode_id"])) - self.connection.action("DELETE FROM tv_episodes WHERE episode_id = ?", [cur_dupe_id["episode_id"]]) + logger.log(u'Deleting duplicate episode with episode_id: ' + str(cur_dupe_id['episode_id'])) + self.connection.action('DELETE FROM tv_episodes WHERE episode_id = ?', [cur_dupe_id['episode_id']]) else: - logger.log(u"No duplicate episode, check passed") + logger.log(u'No duplicate episode, check passed') def fix_orphan_episodes(self): - sqlResults = self.connection.select( - "SELECT episode_id, showid, tv_shows.indexer_id FROM tv_episodes LEFT JOIN tv_shows ON tv_episodes.showid=tv_shows.indexer_id WHERE tv_shows.indexer_id is NULL") + sql_results = self.connection.select( + 'SELECT episode_id, showid, tv_shows.indexer_id FROM tv_episodes LEFT JOIN tv_shows ON tv_episodes.showid=tv_shows.indexer_id WHERE tv_shows.indexer_id is NULL') - for cur_orphan in sqlResults: - logger.log(u"Orphan episode detected! episode_id: " + str(cur_orphan["episode_id"]) + " showid: " + str( - cur_orphan["showid"]), logger.DEBUG) - logger.log(u"Deleting orphan episode with episode_id: " + str(cur_orphan["episode_id"])) - self.connection.action("DELETE FROM tv_episodes WHERE episode_id = ?", [cur_orphan["episode_id"]]) + for cur_orphan in sql_results: + logger.log(u'Orphan episode detected! episode_id: ' + str(cur_orphan['episode_id']) + ' showid: ' + str( + cur_orphan['showid']), logger.DEBUG) + logger.log(u'Deleting orphan episode with episode_id: ' + str(cur_orphan['episode_id'])) + self.connection.action('DELETE FROM tv_episodes WHERE episode_id = ?', [cur_orphan['episode_id']]) else: - logger.log(u"No orphan episodes, check passed") + logger.log(u'No orphan episodes, check passed') def fix_missing_table_indexes(self): - if not self.connection.select("PRAGMA index_info('idx_indexer_id')"): - logger.log(u"Missing idx_indexer_id for TV Shows table detected!, fixing...") - self.connection.action("CREATE UNIQUE INDEX idx_indexer_id ON tv_shows (indexer_id);") + if not self.connection.select('PRAGMA index_info("idx_indexer_id")'): + logger.log(u'Missing idx_indexer_id for TV Shows table detected!, fixing...') + self.connection.action('CREATE UNIQUE INDEX idx_indexer_id ON tv_shows (indexer_id);') - if not self.connection.select("PRAGMA index_info('idx_tv_episodes_showid_airdate')"): - logger.log(u"Missing idx_tv_episodes_showid_airdate for TV Episodes table detected!, fixing...") - self.connection.action("CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate);") + if not self.connection.select('PRAGMA index_info("idx_tv_episodes_showid_airdate")'): + logger.log(u'Missing idx_tv_episodes_showid_airdate for TV Episodes table detected!, fixing...') + self.connection.action('CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate);') - if not self.connection.select("PRAGMA index_info('idx_showid')"): - logger.log(u"Missing idx_showid for TV Episodes table detected!, fixing...") - self.connection.action("CREATE INDEX idx_showid ON tv_episodes (showid);") + if not self.connection.select('PRAGMA index_info("idx_showid")'): + logger.log(u'Missing idx_showid for TV Episodes table detected!, fixing...') + self.connection.action('CREATE INDEX idx_showid ON tv_episodes (showid);') - if not self.connection.select("PRAGMA index_info('idx_status')"): - logger.log(u"Missing idx_status for TV Episodes table detected!, fixing...") - self.connection.action("CREATE INDEX idx_status ON tv_episodes (status,season,episode,airdate)") + if not self.connection.select('PRAGMA index_info("idx_status")'): + logger.log(u'Missing idx_status for TV Episodes table detected!, fixing...') + self.connection.action('CREATE INDEX idx_status ON tv_episodes (status,season,episode,airdate)') - if not self.connection.select("PRAGMA index_info('idx_sta_epi_air')"): - logger.log(u"Missing idx_sta_epi_air for TV Episodes table detected!, fixing...") - self.connection.action("CREATE INDEX idx_sta_epi_air ON tv_episodes (status,episode, airdate)") + if not self.connection.select('PRAGMA index_info("idx_sta_epi_air")'): + logger.log(u'Missing idx_sta_epi_air for TV Episodes table detected!, fixing...') + self.connection.action('CREATE INDEX idx_sta_epi_air ON tv_episodes (status,episode, airdate)') - if not self.connection.select("PRAGMA index_info('idx_sta_epi_sta_air')"): - logger.log(u"Missing idx_sta_epi_sta_air for TV Episodes table detected!, fixing...") - self.connection.action("CREATE INDEX idx_sta_epi_sta_air ON tv_episodes (season,episode, status, airdate)") + if not self.connection.select('PRAGMA index_info("idx_sta_epi_sta_air")'): + logger.log(u'Missing idx_sta_epi_sta_air for TV Episodes table detected!, fixing...') + self.connection.action('CREATE INDEX idx_sta_epi_sta_air ON tv_episodes (season,episode, status, airdate)') def fix_unaired_episodes(self): - curDate = datetime.date.today() + cur_date = datetime.date.today() - sqlResults = self.connection.select( - "SELECT episode_id, showid FROM tv_episodes WHERE airdate > ? AND status in (?,?)", - [curDate.toordinal(), common.SKIPPED, common.WANTED]) + sql_results = self.connection.select( + 'SELECT episode_id, showid FROM tv_episodes WHERE status = ? or airdate > ? AND status in (?,?)', ['', + cur_date.toordinal(), common.SKIPPED, common.WANTED]) - for cur_unaired in sqlResults: - logger.log(u"UNAIRED episode detected! episode_id: " + str(cur_unaired["episode_id"]) + " showid: " + str( - cur_unaired["showid"]), logger.DEBUG) - logger.log(u"Fixing unaired episode status with episode_id: " + str(cur_unaired["episode_id"])) - self.connection.action("UPDATE tv_episodes SET status = ? WHERE episode_id = ?", - [common.UNAIRED, cur_unaired["episode_id"]]) + for cur_unaired in sql_results: + logger.log(u'UNAIRED episode detected! episode_id: ' + str(cur_unaired['episode_id']) + ' showid: ' + str( + cur_unaired['showid']), logger.DEBUG) + logger.log(u'Fixing unaired episode status with episode_id: ' + str(cur_unaired['episode_id'])) + self.connection.action('UPDATE tv_episodes SET status = ? WHERE episode_id = ?', + [common.UNAIRED, cur_unaired['episode_id']]) else: - logger.log(u"No UNAIRED episodes, check passed") + logger.log(u'No UNAIRED episodes, check passed') -def backupDatabase(version): - logger.log(u"Backing up database before upgrade") +def backup_database(version): + logger.log(u'Backing up database before upgrade') if not helpers.backupVersionedFile(db.dbFilename(), version): - logger.log_error_and_exit(u"Database backup failed, abort upgrading database") + logger.log_error_and_exit(u'Database backup failed, abort upgrading database') else: - logger.log(u"Proceeding with upgrade") + logger.log(u'Proceeding with upgrade') # ====================== # = Main DB Migrations = # ====================== # Add new migrations at the bottom of the list; subclass the previous migration. -class InitialSchema(db.SchemaUpgrade): - def test(self): - return self.hasTable("db_version") +# 0 -> 31 +class InitialSchema(db.SchemaUpgrade): def execute(self): - if not self.hasTable("tv_shows") and not self.hasTable("db_version"): + backup_database(self.checkDBVersion()) + + if not self.hasTable('tv_shows') and not self.hasTable('db_version'): queries = [ - "CREATE TABLE db_version (db_version INTEGER);", - "CREATE TABLE history (action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC, quality NUMERIC, resource TEXT, provider TEXT)", - "CREATE TABLE imdb_info (indexer_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT, certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC)", - "CREATE TABLE info (last_backlog NUMERIC, last_indexer NUMERIC, last_proper_search NUMERIC)", - "CREATE TABLE scene_numbering(indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER,scene_season INTEGER, scene_episode INTEGER, PRIMARY KEY(indexer_id, season, episode))", - "CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, indexer_id NUMERIC, indexer TEXT, show_name TEXT, location TEXT, network TEXT, genre TEXT, classification TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, air_by_date NUMERIC, lang TEXT, subtitles NUMERIC, notify_list TEXT, imdb_id TEXT, last_update_indexer NUMERIC, dvdorder NUMERIC, archive_firstmatch NUMERIC, rls_require_words TEXT, rls_ignore_words TEXT, sports NUMERIC);", - "CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC, indexerid NUMERIC, indexer TEXT, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT, file_size NUMERIC, release_name TEXT, subtitles TEXT, subtitles_searchcount NUMERIC, subtitles_lastsearch TIMESTAMP, is_proper NUMERIC, scene_season NUMERIC, scene_episode NUMERIC);", - "CREATE UNIQUE INDEX idx_indexer_id ON tv_shows (indexer_id)", - "CREATE INDEX idx_showid ON tv_episodes (showid);", - "CREATE INDEX idx_sta_epi_air ON tv_episodes (status,episode, airdate);", - "CREATE INDEX idx_sta_epi_sta_air ON tv_episodes (season,episode, status, airdate);", - "CREATE INDEX idx_status ON tv_episodes (status,season,episode,airdate);", - "CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate)", - "INSERT INTO db_version (db_version) VALUES (31);" + 'CREATE TABLE db_version (db_version INTEGER);', + 'CREATE TABLE history (action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC, quality NUMERIC, resource TEXT, provider TEXT)', + 'CREATE TABLE imdb_info (indexer_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT, certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC)', + 'CREATE TABLE info (last_backlog NUMERIC, last_indexer NUMERIC, last_proper_search NUMERIC)', + 'CREATE TABLE scene_numbering(indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER,scene_season INTEGER, scene_episode INTEGER, PRIMARY KEY(indexer_id, season, episode))', + 'CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, indexer_id NUMERIC, indexer TEXT, show_name TEXT, location TEXT, network TEXT, genre TEXT, classification TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, air_by_date NUMERIC, lang TEXT, subtitles NUMERIC, notify_list TEXT, imdb_id TEXT, last_update_indexer NUMERIC, dvdorder NUMERIC, archive_firstmatch NUMERIC, rls_require_words TEXT, rls_ignore_words TEXT, sports NUMERIC);', + 'CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC, indexerid NUMERIC, indexer TEXT, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT, file_size NUMERIC, release_name TEXT, subtitles TEXT, subtitles_searchcount NUMERIC, subtitles_lastsearch TIMESTAMP, is_proper NUMERIC, scene_season NUMERIC, scene_episode NUMERIC);', + 'CREATE UNIQUE INDEX idx_indexer_id ON tv_shows (indexer_id)', + 'CREATE INDEX idx_showid ON tv_episodes (showid);', + 'CREATE INDEX idx_sta_epi_air ON tv_episodes (status,episode, airdate);', + 'CREATE INDEX idx_sta_epi_sta_air ON tv_episodes (season,episode, status, airdate);', + 'CREATE INDEX idx_status ON tv_episodes (status,season,episode,airdate);', + 'CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate)', + 'INSERT INTO db_version (db_version) VALUES (31);' ] for query in queries: self.connection.action(query) @@ -184,63 +187,65 @@ class InitialSchema(db.SchemaUpgrade): cur_db_version = self.checkDBVersion() if cur_db_version < MIN_DB_VERSION: - logger.log_error_and_exit(u"Your database version (" + str( - cur_db_version) + ") is too old to migrate from what this version of SickGear supports (" + \ - str(MIN_DB_VERSION) + ").\n" + \ - "Upgrade using a previous version (tag) build 496 to build 501 of SickGear first or remove database file to begin fresh." - ) + logger.log_error_and_exit(u'Your database version (' + + str(cur_db_version) + + ') is too old to migrate from what this version of SickGear supports (' + + str(MIN_DB_VERSION) + ').' + "\n" + + 'Upgrade using a previous version (tag) build 496 to build 501 of SickGear first or remove database file to begin fresh.' + ) if cur_db_version > MAX_DB_VERSION: - logger.log_error_and_exit(u"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 SickGear, your database may be unusable due to their modifications." - ) + logger.log_error_and_exit(u'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 SickGear, your database may be unusable due to their modifications.' + ) + + return self.checkDBVersion() -class AddSizeAndSceneNameFields(InitialSchema): - def test(self): - return self.checkDBVersion() >= 10 - +# 9 -> 10 +class AddSizeAndSceneNameFields(db.SchemaUpgrade): def execute(self): - backupDatabase(10) + backup_database(self.checkDBVersion()) - if not self.hasColumn("tv_episodes", "file_size"): - self.addColumn("tv_episodes", "file_size") + if not self.hasColumn('tv_episodes', 'file_size'): + self.addColumn('tv_episodes', 'file_size') - if not self.hasColumn("tv_episodes", "release_name"): - self.addColumn("tv_episodes", "release_name", "TEXT", "") + if not self.hasColumn('tv_episodes', 'release_name'): + self.addColumn('tv_episodes', 'release_name', 'TEXT', '') - ep_results = self.connection.select("SELECT episode_id, location, file_size FROM tv_episodes") + ep_results = self.connection.select('SELECT episode_id, location, file_size FROM tv_episodes') - logger.log(u"Adding file size to all episodes in DB, please be patient") + logger.log(u'Adding file size to all episodes in DB, please be patient') for cur_ep in ep_results: - if not cur_ep["location"]: + if not cur_ep['location']: continue # if there is no size yet then populate it for us - if (not cur_ep["file_size"] or not int(cur_ep["file_size"])) and ek.ek(os.path.isfile, cur_ep["location"]): - cur_size = ek.ek(os.path.getsize, cur_ep["location"]) - self.connection.action("UPDATE tv_episodes SET file_size = ? WHERE episode_id = ?", - [cur_size, int(cur_ep["episode_id"])]) + if (not cur_ep['file_size'] or not int(cur_ep['file_size'])) and ek.ek(os.path.isfile, cur_ep['location']): + cur_size = ek.ek(os.path.getsize, cur_ep['location']) + self.connection.action('UPDATE tv_episodes SET file_size = ? WHERE episode_id = ?', + [cur_size, int(cur_ep['episode_id'])]) # check each snatch to see if we can use it to get a release name from - history_results = self.connection.select("SELECT * FROM history WHERE provider != -1 ORDER BY date ASC") + history_results = self.connection.select('SELECT * FROM history WHERE provider != -1 ORDER BY date ASC') - logger.log(u"Adding release name to all episodes still in history") + logger.log(u'Adding release name to all episodes still in history') for cur_result in history_results: # find the associated download, if there isn't one then ignore it download_results = self.connection.select( - "SELECT resource FROM history WHERE provider = -1 AND showid = ? AND season = ? AND episode = ? AND date > ?", - [cur_result["showid"], cur_result["season"], cur_result["episode"], cur_result["date"]]) + 'SELECT resource FROM history WHERE provider = -1 AND showid = ? AND season = ? AND episode = ? AND date > ?', + [cur_result['showid'], cur_result['season'], cur_result['episode'], cur_result['date']]) if not download_results: - logger.log(u"Found a snatch in the history for " + cur_result[ - "resource"] + " but couldn't find the associated download, skipping it", logger.DEBUG) + logger.log(u'Found a snatch in the history for ' + cur_result[ + 'resource'] + ' but couldn\'t find the associated download, skipping it', logger.DEBUG) continue - nzb_name = cur_result["resource"] - file_name = ek.ek(os.path.basename, download_results[0]["resource"]) + nzb_name = cur_result['resource'] + file_name = ek.ek(os.path.basename, download_results[0]['resource']) # take the extension off the filename, it's not needed if '.' in file_name: @@ -248,44 +253,45 @@ class AddSizeAndSceneNameFields(InitialSchema): # find the associated episode on disk ep_results = self.connection.select( - "SELECT episode_id, status FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ? AND location != ''", - [cur_result["showid"], cur_result["season"], cur_result["episode"]]) + 'SELECT episode_id, status FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ? AND location != ""', + [cur_result['showid'], cur_result['season'], cur_result['episode']]) if not ep_results: logger.log( - u"The episode " + nzb_name + " was found in history but doesn't exist on disk anymore, skipping", + u'The episode ' + nzb_name + ' was found in history but doesn\'t exist on disk anymore, skipping', logger.DEBUG) continue # get the status/quality of the existing ep and make sure it's what we expect - ep_status, ep_quality = common.Quality.splitCompositeStatus(int(ep_results[0]["status"])) + ep_status, ep_quality = common.Quality.splitCompositeStatus(int(ep_results[0]['status'])) if ep_status != common.DOWNLOADED: continue - if ep_quality != int(cur_result["quality"]): + if ep_quality != int(cur_result['quality']): continue # make sure this is actually a real release name and not a season pack or something for cur_name in (nzb_name, file_name): - logger.log(u"Checking if " + cur_name + " is actually a good release name", logger.DEBUG) + logger.log(u'Checking if ' + cur_name + ' is actually a good release name', logger.DEBUG) try: np = NameParser(False) parse_result = np.parse(cur_name) except (InvalidNameException, InvalidShowException): continue - if parse_result.series_name and parse_result.season_number != None and parse_result.episode_numbers and parse_result.release_group: + if parse_result.series_name and parse_result.season_number is not None\ + and parse_result.episode_numbers and parse_result.release_group: # if all is well by this point we'll just put the release name into the database - self.connection.action("UPDATE tv_episodes SET release_name = ? WHERE episode_id = ?", - [cur_name, ep_results[0]["episode_id"]]) + self.connection.action('UPDATE tv_episodes SET release_name = ? WHERE episode_id = ?', + [cur_name, ep_results[0]['episode_id']]) break # check each snatch to see if we can use it to get a release name from - empty_results = self.connection.select("SELECT episode_id, location FROM tv_episodes WHERE release_name = ''") + empty_results = self.connection.select('SELECT episode_id, location FROM tv_episodes WHERE release_name = ""') - logger.log(u"Adding release name to all episodes with obvious scene filenames") + logger.log(u'Adding release name to all episodes with obvious scene filenames') for cur_result in empty_results: - ep_file_name = ek.ek(os.path.basename, cur_result["location"]) + ep_file_name = ek.ek(os.path.basename, cur_result['location']) ep_file_name = os.path.splitext(ep_file_name)[0] # only want to find real scene names here so anything with a space in it is out @@ -302,37 +308,41 @@ class AddSizeAndSceneNameFields(InitialSchema): continue logger.log( - u"Name " + ep_file_name + " gave release group of " + parse_result.release_group + ", seems valid", + u'Name ' + ep_file_name + ' gave release group of ' + parse_result.release_group + ', seems valid', logger.DEBUG) - self.connection.action("UPDATE tv_episodes SET release_name = ? WHERE episode_id = ?", - [ep_file_name, cur_result["episode_id"]]) + self.connection.action('UPDATE tv_episodes SET release_name = ? WHERE episode_id = ?', + [ep_file_name, cur_result['episode_id']]) self.incDBVersion() + return self.checkDBVersion() -class RenameSeasonFolders(AddSizeAndSceneNameFields): - def test(self): - return self.checkDBVersion() >= 11 - +# 10 -> 11 +class RenameSeasonFolders(db.SchemaUpgrade): def execute(self): + backup_database(self.checkDBVersion()) + # rename the column - self.connection.action("ALTER TABLE tv_shows RENAME TO tmp_tv_shows") + self.connection.action('ALTER TABLE tv_shows RENAME TO tmp_tv_shows') self.connection.action( - "CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, location TEXT, show_name TEXT, tvdb_id NUMERIC, network TEXT, genre TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, tvr_id NUMERIC, tvr_name TEXT, air_by_date NUMERIC, lang TEXT)") - sql = "INSERT INTO tv_shows(show_id, location, show_name, tvdb_id, network, genre, runtime, quality, airs, status, flatten_folders, paused, startyear, tvr_id, tvr_name, air_by_date, lang) SELECT show_id, location, show_name, tvdb_id, network, genre, runtime, quality, airs, status, seasonfolders, paused, startyear, tvr_id, tvr_name, air_by_date, lang FROM tmp_tv_shows" + 'CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, location TEXT, show_name TEXT, tvdb_id NUMERIC, network TEXT, genre TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, tvr_id NUMERIC, tvr_name TEXT, air_by_date NUMERIC, lang TEXT)') + sql = 'INSERT INTO tv_shows(show_id, location, show_name, tvdb_id, network, genre, runtime, quality, airs, status, flatten_folders, paused, startyear, tvr_id, tvr_name, air_by_date, lang) SELECT show_id, location, show_name, tvdb_id, network, genre, runtime, quality, airs, status, seasonfolders, paused, startyear, tvr_id, tvr_name, air_by_date, lang FROM tmp_tv_shows' self.connection.action(sql) # flip the values to be opposite of what they were before - self.connection.action("UPDATE tv_shows SET flatten_folders = 2 WHERE flatten_folders = 1") - self.connection.action("UPDATE tv_shows SET flatten_folders = 1 WHERE flatten_folders = 0") - self.connection.action("UPDATE tv_shows SET flatten_folders = 0 WHERE flatten_folders = 2") - self.connection.action("DROP TABLE tmp_tv_shows") + self.connection.action('UPDATE tv_shows SET flatten_folders = 2 WHERE flatten_folders = 1') + self.connection.action('UPDATE tv_shows SET flatten_folders = 1 WHERE flatten_folders = 0') + self.connection.action('UPDATE tv_shows SET flatten_folders = 0 WHERE flatten_folders = 2') + self.connection.action('DROP TABLE tmp_tv_shows') self.incDBVersion() + return self.checkDBVersion() -class Add1080pAndRawHDQualities(RenameSeasonFolders): - """Add support for 1080p related qualities along with RawHD +# 11 -> 12 +class Add1080pAndRawHDQualities(db.SchemaUpgrade): + """ + Add support for 1080p related qualities along with RawHD Quick overview of what the upgrade needs to do: @@ -347,15 +357,13 @@ class Add1080pAndRawHDQualities(RenameSeasonFolders): fullhdwebdl | | 1<<6 """ - def test(self): - return self.checkDBVersion() >= 12 - def _update_status(self, old_status): (status, quality) = common.Quality.splitCompositeStatus(old_status) return common.Quality.compositeStatus(status, self._update_quality(quality)) def _update_quality(self, old_quality): - """Update bitwise flags to reflect new quality values + """ + Update bitwise flags to reflect new quality values Check flag bits (clear old then set their new locations) starting with the highest bits so we dont overwrite data we need later on @@ -363,30 +371,31 @@ class Add1080pAndRawHDQualities(RenameSeasonFolders): result = old_quality # move fullhdbluray from 1<<5 to 1<<8 if set - if (result & (1 << 5)): - result = result & ~(1 << 5) - result = result | (1 << 8) + if result & (1 << 5): + result &= ~(1 << 5) + result |= 1 << 8 # move hdbluray from 1<<4 to 1<<7 if set - if (result & (1 << 4)): - result = result & ~(1 << 4) - result = result | (1 << 7) + if result & (1 << 4): + result &= ~(1 << 4) + result |= 1 << 7 # move hdwebdl from 1<<3 to 1<<5 if set - if (result & (1 << 3)): - result = result & ~(1 << 3) - result = result | (1 << 5) + if result & (1 << 3): + result &= ~(1 << 3) + result |= 1 << 5 return result def _update_composite_qualities(self, status): - """Unpack, Update, Return new quality values + ''' + Unpack, Update, Return new quality values Unpack the composite archive/initial values. Update either qualities if needed. Then return the new compsite quality value. - """ + ''' best = (status & (0xffff << 16)) >> 16 - initial = status & (0xffff) + initial = status & 0xffff best = self._update_quality(best) initial = self._update_quality(initial) @@ -395,7 +404,7 @@ class Add1080pAndRawHDQualities(RenameSeasonFolders): return result def execute(self): - backupDatabase(self.checkDBVersion()) + backup_database(self.checkDBVersion()) # update the default quality so we dont grab the wrong qualities after migration sickbeard.QUALITY_DEFAULT = self._update_composite_qualities(sickbeard.QUALITY_DEFAULT) @@ -404,8 +413,8 @@ class Add1080pAndRawHDQualities(RenameSeasonFolders): # upgrade previous HD to HD720p -- shift previous qualities to new placevalues old_hd = common.Quality.combineQualities( [common.Quality.HDTV, common.Quality.HDWEBDL >> 2, common.Quality.HDBLURAY >> 3], []) - new_hd = common.Quality.combineQualities([common.Quality.HDTV, common.Quality.HDWEBDL, common.Quality.HDBLURAY], - []) + new_hd = common.Quality.combineQualities([common.Quality.HDTV, common.Quality.HDWEBDL, + common.Quality.HDBLURAY], []) # update ANY -- shift existing qualities and add new 1080p qualities, note that rawHD was not added to the ANY template old_any = common.Quality.combineQualities( @@ -417,501 +426,537 @@ class Add1080pAndRawHDQualities(RenameSeasonFolders): common.Quality.UNKNOWN], []) # update qualities (including templates) - logger.log(u"[1/4] Updating pre-defined templates and the quality for each show...", logger.MESSAGE) + logger.log(u'[1/4] Updating pre-defined templates and the quality for each show...', logger.MESSAGE) cl = [] - shows = self.connection.select("SELECT * FROM tv_shows") + shows = self.connection.select('SELECT * FROM tv_shows') for cur_show in shows: - if cur_show["quality"] == old_hd: + if old_hd == cur_show['quality']: new_quality = new_hd - elif cur_show["quality"] == old_any: + elif old_any == cur_show['quality']: new_quality = new_any else: - new_quality = self._update_composite_qualities(cur_show["quality"]) - cl.append(["UPDATE tv_shows SET quality = ? WHERE show_id = ?", [new_quality, cur_show["show_id"]]]) + new_quality = self._update_composite_qualities(cur_show['quality']) + cl.append(['UPDATE tv_shows SET quality = ? WHERE show_id = ?', [new_quality, cur_show['show_id']]]) self.connection.mass_action(cl) # update status that are are within the old hdwebdl (1<<3 which is 8) and better -- exclude unknown (1<<15 which is 32768) - logger.log(u"[2/4] Updating the status for the episodes within each show...", logger.MESSAGE) + logger.log(u'[2/4] Updating the status for the episodes within each show...', logger.MESSAGE) cl = [] - episodes = self.connection.select("SELECT * FROM tv_episodes WHERE status < 3276800 AND status >= 800") + episodes = self.connection.select('SELECT * FROM tv_episodes WHERE status < 3276800 AND status >= 800') for cur_episode in episodes: - cl.append(["UPDATE tv_episodes SET status = ? WHERE episode_id = ?", - [self._update_status(cur_episode["status"]), cur_episode["episode_id"]]]) + cl.append(['UPDATE tv_episodes SET status = ? WHERE episode_id = ?', + [self._update_status(cur_episode['status']), cur_episode['episode_id']]]) self.connection.mass_action(cl) # make two seperate passes through the history since snatched and downloaded (action & quality) may not always coordinate together # update previous history so it shows the correct action - logger.log(u"[3/4] Updating history to reflect the correct action...", logger.MESSAGE) + logger.log(u'[3/4] Updating history to reflect the correct action...', logger.MESSAGE) cl = [] - historyAction = self.connection.select("SELECT * FROM history WHERE action < 3276800 AND action >= 800") - for cur_entry in historyAction: - cl.append(["UPDATE history SET action = ? WHERE showid = ? AND date = ?", - [self._update_status(cur_entry["action"]), cur_entry["showid"], cur_entry["date"]]]) + history_action = self.connection.select('SELECT * FROM history WHERE action < 3276800 AND action >= 800') + for cur_entry in history_action: + cl.append(['UPDATE history SET action = ? WHERE showid = ? AND date = ?', + [self._update_status(cur_entry['action']), cur_entry['showid'], cur_entry['date']]]) self.connection.mass_action(cl) # update previous history so it shows the correct quality - logger.log(u"[4/4] Updating history to reflect the correct quality...", logger.MESSAGE) + logger.log(u'[4/4] Updating history to reflect the correct quality...', logger.MESSAGE) cl = [] - historyQuality = self.connection.select("SELECT * FROM history WHERE quality < 32768 AND quality >= 8") - for cur_entry in historyQuality: - cl.append(["UPDATE history SET quality = ? WHERE showid = ? AND date = ?", - [self._update_quality(cur_entry["quality"]), cur_entry["showid"], cur_entry["date"]]]) + history_quality = self.connection.select('SELECT * FROM history WHERE quality < 32768 AND quality >= 8') + for cur_entry in history_quality: + cl.append(['UPDATE history SET quality = ? WHERE showid = ? AND date = ?', + [self._update_quality(cur_entry['quality']), cur_entry['showid'], cur_entry['date']]]) self.connection.mass_action(cl) self.incDBVersion() # cleanup and reduce db if any previous data was removed - logger.log(u"Performing a vacuum on the database.", logger.DEBUG) - self.connection.action("VACUUM") + logger.log(u'Performing a vacuum on the database.', logger.DEBUG) + self.connection.action('VACUUM') + return self.checkDBVersion() -class AddShowidTvdbidIndex(Add1080pAndRawHDQualities): - """ Adding index on tvdb_id (tv_shows) and showid (tv_episodes) to speed up searches/queries """ - - def test(self): - return self.checkDBVersion() >= 13 +# 12 -> 13 +class AddShowidTvdbidIndex(db.SchemaUpgrade): + # Adding index on tvdb_id (tv_shows) and showid (tv_episodes) to speed up searches/queries def execute(self): - backupDatabase(13) + backup_database(self.checkDBVersion()) - logger.log(u"Check for duplicate shows before adding unique index.") + logger.log(u'Check for duplicate shows before adding unique index.') MainSanityCheck(self.connection).fix_duplicate_shows('tvdb_id') - logger.log(u"Adding index on tvdb_id (tv_shows) and showid (tv_episodes) to speed up searches/queries.") - if not self.hasTable("idx_showid"): - self.connection.action("CREATE INDEX idx_showid ON tv_episodes (showid);") - if not self.hasTable("idx_tvdb_id"): - self.connection.action("CREATE UNIQUE INDEX idx_tvdb_id ON tv_shows (tvdb_id);") + logger.log(u'Adding index on tvdb_id (tv_shows) and showid (tv_episodes) to speed up searches/queries.') + if not self.hasTable('idx_showid'): + self.connection.action('CREATE INDEX idx_showid ON tv_episodes (showid);') + if not self.hasTable('idx_tvdb_id'): + self.connection.action('CREATE UNIQUE INDEX idx_tvdb_id ON tv_shows (tvdb_id);') + + self.incDBVersion() + return self.checkDBVersion() + + +# 13 -> 14 +class AddLastUpdateTVDB(db.SchemaUpgrade): + # Adding column last_update_tvdb to tv_shows for controlling nightly updates + def execute(self): + backup_database(self.checkDBVersion()) + + logger.log(u'Adding column last_update_tvdb to tvshows') + if not self.hasColumn('tv_shows', 'last_update_tvdb'): + self.addColumn('tv_shows', 'last_update_tvdb', default=1) self.incDBVersion() -class AddLastUpdateTVDB(AddShowidTvdbidIndex): - """ Adding column last_update_tvdb to tv_shows for controlling nightly updates """ - - def test(self): - return self.checkDBVersion() >= 14 - +# 14 -> 15 +class AddDBIncreaseTo15(db.SchemaUpgrade): def execute(self): - backupDatabase(14) - - logger.log(u"Adding column last_update_tvdb to tvshows") - if not self.hasColumn("tv_shows", "last_update_tvdb"): - self.addColumn("tv_shows", "last_update_tvdb", default=1) + backup_database(self.checkDBVersion()) self.incDBVersion() + return self.checkDBVersion() -class AddDBIncreaseTo15(AddLastUpdateTVDB): - def test(self): - return self.checkDBVersion() >= 15 - +# 15 -> 16 +class AddIMDbInfo(db.SchemaUpgrade): def execute(self): - self.incDBVersion() + backup_database(self.checkDBVersion()) - -class AddIMDbInfo(AddDBIncreaseTo15): - def test(self): - return self.checkDBVersion() >= 16 - - def execute(self): self.connection.action( - "CREATE TABLE imdb_info (tvdb_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT, certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC)") + 'CREATE TABLE imdb_info (tvdb_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT, certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC)') - if not self.hasColumn("tv_shows", "imdb_id"): - self.addColumn("tv_shows", "imdb_id") + if not self.hasColumn('tv_shows', 'imdb_id'): + self.addColumn('tv_shows', 'imdb_id') self.incDBVersion() + return self.checkDBVersion() -class AddProperNamingSupport(AddIMDbInfo): - def test(self): - return self.checkDBVersion() >= 17 - +# 16 -> 17 +class AddProperNamingSupport(db.SchemaUpgrade): def execute(self): - self.addColumn("tv_episodes", "is_proper") + backup_database(self.checkDBVersion()) + + self.addColumn('tv_episodes', 'is_proper') self.incDBVersion() + return self.checkDBVersion() -class AddEmailSubscriptionTable(AddProperNamingSupport): - def test(self): - return self.checkDBVersion() >= 18 - +# 17 -> 18 +class AddEmailSubscriptionTable(db.SchemaUpgrade): def execute(self): + backup_database(self.checkDBVersion()) + self.addColumn('tv_shows', 'notify_list', 'TEXT', None) self.incDBVersion() + return self.checkDBVersion() -class AddProperSearch(AddEmailSubscriptionTable): - def test(self): - return self.checkDBVersion() >= 19 - +# 18 -> 19 +class AddProperSearch(db.SchemaUpgrade): def execute(self): - backupDatabase(19) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column last_proper_search to info") - if not self.hasColumn("info", "last_proper_search"): - self.addColumn("info", "last_proper_search", default=1) + logger.log(u'Adding column last_proper_search to info') + if not self.hasColumn('info', 'last_proper_search'): + self.addColumn('info', 'last_proper_search', default=1) self.incDBVersion() + return self.checkDBVersion() -class AddDvdOrderOption(AddProperSearch): - def test(self): - return self.checkDBVersion() >= 20 - +# 19 -> 20 +class AddDvdOrderOption(db.SchemaUpgrade): def execute(self): - logger.log(u"Adding column dvdorder to tvshows") - if not self.hasColumn("tv_shows", "dvdorder"): - self.addColumn("tv_shows", "dvdorder", "NUMERIC", "0") + backup_database(self.checkDBVersion()) + + logger.log(u'Adding column dvdorder to tvshows') + if not self.hasColumn('tv_shows', 'dvdorder'): + self.addColumn('tv_shows', 'dvdorder', 'NUMERIC', '0') self.incDBVersion() + return self.checkDBVersion() -class AddSubtitlesSupport(AddDvdOrderOption): - def test(self): - return self.checkDBVersion() >= 21 - +# 20 -> 21 +class AddSubtitlesSupport(db.SchemaUpgrade): def execute(self): - if not self.hasColumn("tv_shows", "subtitles"): - self.addColumn("tv_shows", "subtitles") - self.addColumn("tv_episodes", "subtitles", "TEXT", "") - self.addColumn("tv_episodes", "subtitles_searchcount") - self.addColumn("tv_episodes", "subtitles_lastsearch", "TIMESTAMP", str(datetime.datetime.min)) + backup_database(self.checkDBVersion()) + + if not self.hasColumn('tv_shows', 'subtitles'): + self.addColumn('tv_shows', 'subtitles') + self.addColumn('tv_episodes', 'subtitles', 'TEXT', '') + self.addColumn('tv_episodes', 'subtitles_searchcount') + self.addColumn('tv_episodes', 'subtitles_lastsearch', 'TIMESTAMP', str(datetime.datetime.min)) self.incDBVersion() + return self.checkDBVersion() -class ConvertTVShowsToIndexerScheme(AddSubtitlesSupport): - def test(self): - return self.checkDBVersion() >= 22 - +# 21 -> 22 +class ConvertTVShowsToIndexerScheme(db.SchemaUpgrade): def execute(self): - backupDatabase(22) + backup_database(self.checkDBVersion()) - logger.log(u"Converting TV Shows table to Indexer Scheme...") + logger.log(u'Converting TV Shows table to Indexer Scheme...') - if self.hasTable("tmp_tv_shows"): - logger.log(u"Removing temp tv show tables left behind from previous updates...") - self.connection.action("DROP TABLE tmp_tv_shows") + if self.hasTable('tmp_tv_shows'): + logger.log(u'Removing temp tv show tables left behind from previous updates...') + self.connection.action('DROP TABLE tmp_tv_shows') - self.connection.action("ALTER TABLE tv_shows RENAME TO tmp_tv_shows") + self.connection.action('ALTER TABLE tv_shows RENAME TO tmp_tv_shows') self.connection.action( - "CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, indexer_id NUMERIC, indexer NUMERIC, show_name TEXT, location TEXT, network TEXT, genre TEXT, classification TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, air_by_date NUMERIC, lang TEXT, subtitles NUMERIC, notify_list TEXT, imdb_id TEXT, last_update_indexer NUMERIC, dvdorder NUMERIC)") + 'CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, indexer_id NUMERIC, indexer NUMERIC, show_name TEXT, location TEXT, network TEXT, genre TEXT, classification TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, air_by_date NUMERIC, lang TEXT, subtitles NUMERIC, notify_list TEXT, imdb_id TEXT, last_update_indexer NUMERIC, dvdorder NUMERIC)') self.connection.action( - "INSERT INTO tv_shows(show_id, indexer_id, show_name, location, network, genre, runtime, quality, airs, status, flatten_folders, paused, startyear, air_by_date, lang, subtitles, notify_list, imdb_id, last_update_indexer, dvdorder) SELECT show_id, tvdb_id, show_name, location, network, genre, runtime, quality, airs, status, flatten_folders, paused, startyear, air_by_date, lang, subtitles, notify_list, imdb_id, last_update_tvdb, dvdorder FROM tmp_tv_shows") - self.connection.action("DROP TABLE tmp_tv_shows") + 'INSERT INTO tv_shows(show_id, indexer_id, show_name, location, network, genre, runtime, quality, airs, status, flatten_folders, paused, startyear, air_by_date, lang, subtitles, notify_list, imdb_id, last_update_indexer, dvdorder) SELECT show_id, tvdb_id, show_name, location, network, genre, runtime, quality, airs, status, flatten_folders, paused, startyear, air_by_date, lang, subtitles, notify_list, imdb_id, last_update_tvdb, dvdorder FROM tmp_tv_shows') + self.connection.action('DROP TABLE tmp_tv_shows') - self.connection.action("CREATE UNIQUE INDEX idx_indexer_id ON tv_shows (indexer_id);") + self.connection.action('CREATE UNIQUE INDEX idx_indexer_id ON tv_shows (indexer_id);') - self.connection.action("UPDATE tv_shows SET classification = 'Scripted'") - self.connection.action("UPDATE tv_shows SET indexer = 1") + self.connection.action('UPDATE tv_shows SET classification = "Scripted"') + self.connection.action('UPDATE tv_shows SET indexer = 1') self.incDBVersion() + return self.checkDBVersion() -class ConvertTVEpisodesToIndexerScheme(ConvertTVShowsToIndexerScheme): - def test(self): - return self.checkDBVersion() >= 23 - +# 22 -> 23 +class ConvertTVEpisodesToIndexerScheme(db.SchemaUpgrade): def execute(self): - backupDatabase(23) + backup_database(self.checkDBVersion()) - logger.log(u"Converting TV Episodes table to Indexer Scheme...") + logger.log(u'Converting TV Episodes table to Indexer Scheme...') - if self.hasTable("tmp_tv_episodes"): - logger.log(u"Removing temp tv episode tables left behind from previous updates...") - self.connection.action("DROP TABLE tmp_tv_episodes") + if self.hasTable('tmp_tv_episodes'): + logger.log(u'Removing temp tv episode tables left behind from previous updates...') + self.connection.action('DROP TABLE tmp_tv_episodes') - self.connection.action("ALTER TABLE tv_episodes RENAME TO tmp_tv_episodes") + self.connection.action('ALTER TABLE tv_episodes RENAME TO tmp_tv_episodes') self.connection.action( - "CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC, indexerid NUMERIC, indexer NUMERIC, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT, file_size NUMERIC, release_name TEXT, subtitles TEXT, subtitles_searchcount NUMERIC, subtitles_lastsearch TIMESTAMP, is_proper NUMERIC)") + 'CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC, indexerid NUMERIC, indexer NUMERIC, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT, file_size NUMERIC, release_name TEXT, subtitles TEXT, subtitles_searchcount NUMERIC, subtitles_lastsearch TIMESTAMP, is_proper NUMERIC)') self.connection.action( - "INSERT INTO tv_episodes(episode_id, showid, indexerid, name, season, episode, description, airdate, hasnfo, hastbn, status, location, file_size, release_name, subtitles, subtitles_searchcount, subtitles_lastsearch, is_proper) SELECT episode_id, showid, tvdbid, name, season, episode, description, airdate, hasnfo, hastbn, status, location, file_size, release_name, subtitles, subtitles_searchcount, subtitles_lastsearch, is_proper FROM tmp_tv_episodes") - self.connection.action("DROP TABLE tmp_tv_episodes") + 'INSERT INTO tv_episodes(episode_id, showid, indexerid, name, season, episode, description, airdate, hasnfo, hastbn, status, location, file_size, release_name, subtitles, subtitles_searchcount, subtitles_lastsearch, is_proper) SELECT episode_id, showid, tvdbid, name, season, episode, description, airdate, hasnfo, hastbn, status, location, file_size, release_name, subtitles, subtitles_searchcount, subtitles_lastsearch, is_proper FROM tmp_tv_episodes') + self.connection.action('DROP TABLE tmp_tv_episodes') - self.connection.action("CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate);") - self.connection.action("CREATE INDEX idx_showid ON tv_episodes (showid);") - self.connection.action("CREATE INDEX idx_status ON tv_episodes (status,season,episode,airdate)") - self.connection.action("CREATE INDEX idx_sta_epi_air ON tv_episodes (status,episode, airdate)") - self.connection.action("CREATE INDEX idx_sta_epi_sta_air ON tv_episodes (season,episode, status, airdate)") + self.connection.action('CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate);') + self.connection.action('CREATE INDEX idx_showid ON tv_episodes (showid);') + self.connection.action('CREATE INDEX idx_status ON tv_episodes (status,season,episode,airdate)') + self.connection.action('CREATE INDEX idx_sta_epi_air ON tv_episodes (status,episode, airdate)') + self.connection.action('CREATE INDEX idx_sta_epi_sta_air ON tv_episodes (season,episode, status, airdate)') - self.connection.action("UPDATE tv_episodes SET indexer = 1") + self.connection.action('UPDATE tv_episodes SET indexer = 1') self.incDBVersion() + return self.checkDBVersion() -class ConvertIMDBInfoToIndexerScheme(ConvertTVEpisodesToIndexerScheme): - def test(self): - return self.checkDBVersion() >= 24 - +# 23 -> 24 +class ConvertIMDBInfoToIndexerScheme(db.SchemaUpgrade): def execute(self): - backupDatabase(24) + backup_database(self.checkDBVersion()) - logger.log(u"Converting IMDB Info table to Indexer Scheme...") + logger.log(u'Converting IMDB Info table to Indexer Scheme...') - if self.hasTable("tmp_imdb_info"): - logger.log(u"Removing temp imdb info tables left behind from previous updates...") - self.connection.action("DROP TABLE tmp_imdb_info") + if self.hasTable('tmp_imdb_info'): + logger.log(u'Removing temp imdb info tables left behind from previous updates...') + self.connection.action('DROP TABLE tmp_imdb_info') - self.connection.action("ALTER TABLE imdb_info RENAME TO tmp_imdb_info") + self.connection.action('ALTER TABLE imdb_info RENAME TO tmp_imdb_info') self.connection.action( - "CREATE TABLE imdb_info (indexer_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT, certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC)") + 'CREATE TABLE imdb_info (indexer_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT, certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC)') self.connection.action( - "INSERT INTO imdb_info(indexer_id, imdb_id, title, year, akas, runtimes, genres, countries, country_codes, certificates, rating, votes, last_update) SELECT tvdb_id, imdb_id, title, year, akas, runtimes, genres, countries, country_codes, certificates, rating, votes, last_update FROM tmp_imdb_info") - self.connection.action("DROP TABLE tmp_imdb_info") + 'INSERT INTO imdb_info(indexer_id, imdb_id, title, year, akas, runtimes, genres, countries, country_codes, certificates, rating, votes, last_update) SELECT tvdb_id, imdb_id, title, year, akas, runtimes, genres, countries, country_codes, certificates, rating, votes, last_update FROM tmp_imdb_info') + self.connection.action('DROP TABLE tmp_imdb_info') self.incDBVersion() + return self.checkDBVersion() -class ConvertInfoToIndexerScheme(ConvertIMDBInfoToIndexerScheme): - def test(self): - return self.checkDBVersion() >= 25 - +# 24 -> 25 +class ConvertInfoToIndexerScheme(db.SchemaUpgrade): def execute(self): - backupDatabase(25) + backup_database(self.checkDBVersion()) - logger.log(u"Converting Info table to Indexer Scheme...") + logger.log(u'Converting Info table to Indexer Scheme...') - if self.hasTable("tmp_info"): - logger.log(u"Removing temp info tables left behind from previous updates...") - self.connection.action("DROP TABLE tmp_info") + if self.hasTable('tmp_info'): + logger.log(u'Removing temp info tables left behind from previous updates...') + self.connection.action('DROP TABLE tmp_info') - self.connection.action("ALTER TABLE info RENAME TO tmp_info") + self.connection.action('ALTER TABLE info RENAME TO tmp_info') self.connection.action( - "CREATE TABLE info (last_backlog NUMERIC, last_indexer NUMERIC, last_proper_search NUMERIC)") + 'CREATE TABLE info (last_backlog NUMERIC, last_indexer NUMERIC, last_proper_search NUMERIC)') self.connection.action( - "INSERT INTO info(last_backlog, last_indexer, last_proper_search) SELECT last_backlog, last_tvdb, last_proper_search FROM tmp_info") - self.connection.action("DROP TABLE tmp_info") + 'INSERT INTO info(last_backlog, last_indexer, last_proper_search) SELECT last_backlog, last_tvdb, last_proper_search FROM tmp_info') + self.connection.action('DROP TABLE tmp_info') self.incDBVersion() + return self.checkDBVersion() -class AddArchiveFirstMatchOption(ConvertInfoToIndexerScheme): - def test(self): - return self.checkDBVersion() >= 26 - +# 25 -> 26 +class AddArchiveFirstMatchOption(db.SchemaUpgrade): def execute(self): - backupDatabase(26) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column archive_firstmatch to tvshows") - if not self.hasColumn("tv_shows", "archive_firstmatch"): - self.addColumn("tv_shows", "archive_firstmatch", "NUMERIC", "0") + logger.log(u'Adding column archive_firstmatch to tvshows') + if not self.hasColumn('tv_shows', 'archive_firstmatch'): + self.addColumn('tv_shows', 'archive_firstmatch', 'NUMERIC', '0') self.incDBVersion() + return self.checkDBVersion() -class AddSceneNumbering(AddArchiveFirstMatchOption): - def test(self): - return self.checkDBVersion() >= 27 +# 26 -> 27 +class AddSceneNumbering(db.SchemaUpgrade): def execute(self): - backupDatabase(27) + backup_database(self.checkDBVersion()) - if self.hasTable("scene_numbering"): - self.connection.action("DROP TABLE scene_numbering") + if self.hasTable('scene_numbering'): + self.connection.action('DROP TABLE scene_numbering') self.connection.action( - "CREATE TABLE scene_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER, scene_season INTEGER, scene_episode INTEGER, PRIMARY KEY (indexer_id, season, episode, scene_season, scene_episode))") + 'CREATE TABLE scene_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER, scene_season INTEGER, scene_episode INTEGER, PRIMARY KEY (indexer_id, season, episode, scene_season, scene_episode))') self.incDBVersion() + return self.checkDBVersion() -class ConvertIndexerToInteger(AddSceneNumbering): - def test(self): - return self.checkDBVersion() >= 28 - +# 27 -> 28 +class ConvertIndexerToInteger(db.SchemaUpgrade): def execute(self): - backupDatabase(28) + backup_database(self.checkDBVersion()) cl = [] - logger.log(u"Converting Indexer to Integer ...", logger.MESSAGE) - cl.append(["UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?", ["1", "tvdb"]]) - cl.append(["UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?", ["2", "tvrage"]]) - cl.append(["UPDATE tv_episodes SET indexer = ? WHERE LOWER(indexer) = ?", ["1", "tvdb"]]) - cl.append(["UPDATE tv_episodes SET indexer = ? WHERE LOWER(indexer) = ?", ["2", "tvrage"]]) - cl.append(["UPDATE scene_numbering SET indexer = ? WHERE LOWER(indexer) = ?", ["1", "tvdb"]]) - cl.append(["UPDATE scene_numbering SET indexer = ? WHERE LOWER(indexer) = ?", ["2", "tvrage"]]) + logger.log(u'Converting Indexer to Integer ...', logger.MESSAGE) + cl.append(['UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?', ['1', 'tvdb']]) + cl.append(['UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?', ['2', 'tvrage']]) + cl.append(['UPDATE tv_episodes SET indexer = ? WHERE LOWER(indexer) = ?', ['1', 'tvdb']]) + cl.append(['UPDATE tv_episodes SET indexer = ? WHERE LOWER(indexer) = ?', ['2', 'tvrage']]) + cl.append(['UPDATE scene_numbering SET indexer = ? WHERE LOWER(indexer) = ?', ['1', 'tvdb']]) + cl.append(['UPDATE scene_numbering SET indexer = ? WHERE LOWER(indexer) = ?', ['2', 'tvrage']]) self.connection.mass_action(cl) self.incDBVersion() + return self.checkDBVersion() -class AddRequireAndIgnoreWords(ConvertIndexerToInteger): - """ Adding column rls_require_words and rls_ignore_words to tv_shows """ - - def test(self): - return self.checkDBVersion() >= 29 +# 28 -> 29 +class AddRequireAndIgnoreWords(db.SchemaUpgrade): + # Adding column rls_require_words and rls_ignore_words to tv_shows def execute(self): - backupDatabase(29) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column rls_require_words to tvshows") - if not self.hasColumn("tv_shows", "rls_require_words"): - self.addColumn("tv_shows", "rls_require_words", "TEXT", "") + logger.log(u'Adding column rls_require_words to tvshows') + if not self.hasColumn('tv_shows', 'rls_require_words'): + self.addColumn('tv_shows', 'rls_require_words', 'TEXT', '') - logger.log(u"Adding column rls_ignore_words to tvshows") - if not self.hasColumn("tv_shows", "rls_ignore_words"): - self.addColumn("tv_shows", "rls_ignore_words", "TEXT", "") + logger.log(u'Adding column rls_ignore_words to tvshows') + if not self.hasColumn('tv_shows', 'rls_ignore_words'): + self.addColumn('tv_shows', 'rls_ignore_words', 'TEXT', '') self.incDBVersion() + return self.checkDBVersion() -class AddSportsOption(AddRequireAndIgnoreWords): - def test(self): - return self.checkDBVersion() >= 30 - +# 29 -> 30 +class AddSportsOption(db.SchemaUpgrade): def execute(self): - backupDatabase(30) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column sports to tvshows") - if not self.hasColumn("tv_shows", "sports"): - self.addColumn("tv_shows", "sports", "NUMERIC", "0") + logger.log(u'Adding column sports to tvshows') + if not self.hasColumn('tv_shows', 'sports'): + self.addColumn('tv_shows', 'sports', 'NUMERIC', '0') - if self.hasColumn("tv_shows", "air_by_date") and self.hasColumn("tv_shows", "sports"): + if self.hasColumn('tv_shows', 'air_by_date') and self.hasColumn('tv_shows', 'sports'): # update sports column - logger.log(u"[4/4] Updating tv_shows to reflect the correct sports value...", logger.MESSAGE) + logger.log(u'[4/4] Updating tv_shows to reflect the correct sports value...', logger.MESSAGE) cl = [] - historyQuality = self.connection.select( - "SELECT * FROM tv_shows WHERE LOWER(classification) = 'sports' AND air_by_date = 1 AND sports = 0") - for cur_entry in historyQuality: - cl.append(["UPDATE tv_shows SET sports = ? WHERE show_id = ?", - [cur_entry["air_by_date"], cur_entry["show_id"]]]) - cl.append(["UPDATE tv_shows SET air_by_date = 0 WHERE show_id = ?", [cur_entry["show_id"]]]) + history_quality = self.connection.select( + 'SELECT * FROM tv_shows WHERE LOWER(classification) = "sports" AND air_by_date = 1 AND sports = 0') + for cur_entry in history_quality: + cl.append(['UPDATE tv_shows SET sports = ? WHERE show_id = ?', + [cur_entry['air_by_date'], cur_entry['show_id']]]) + cl.append(['UPDATE tv_shows SET air_by_date = 0 WHERE show_id = ?', [cur_entry['show_id']]]) self.connection.mass_action(cl) self.incDBVersion() + return self.checkDBVersion() -class AddSceneNumberingToTvEpisodes(AddSportsOption): - def test(self): - return self.checkDBVersion() >= 31 - +# 30 -> 31 +class AddSceneNumberingToTvEpisodes(db.SchemaUpgrade): def execute(self): - backupDatabase(31) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column scene_season and scene_episode to tvepisodes") - self.addColumn("tv_episodes", "scene_season", "NUMERIC", "NULL") - self.addColumn("tv_episodes", "scene_episode", "NUMERIC", "NULL") + logger.log(u'Adding column scene_season and scene_episode to tvepisodes') + self.addColumn('tv_episodes', 'scene_season', 'NUMERIC', 'NULL') + self.addColumn('tv_episodes', 'scene_episode', 'NUMERIC', 'NULL') self.incDBVersion() + return self.incDBVersion() -class AddAnimeTVShow(AddSceneNumberingToTvEpisodes): - def test(self): - return self.checkDBVersion() >= 32 +# 31 -> 32 +class AddAnimeTVShow(db.SchemaUpgrade): def execute(self): - backupDatabase(32) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column anime to tv_episodes") - self.addColumn("tv_shows", "anime", "NUMERIC", "0") + logger.log(u'Adding column anime to tv_episodes') + self.addColumn('tv_shows', 'anime', 'NUMERIC', '0') self.incDBVersion() + return self.checkDBVersion() -class AddAbsoluteNumbering(AddAnimeTVShow): - def test(self): - return self.checkDBVersion() >= 33 +# 32 -> 33 +class AddAbsoluteNumbering(db.SchemaUpgrade): def execute(self): - backupDatabase(33) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column absolute_number to tv_episodes") - self.addColumn("tv_episodes", "absolute_number", "NUMERIC", "0") + logger.log(u'Adding column absolute_number to tv_episodes') + self.addColumn('tv_episodes', 'absolute_number', 'NUMERIC', '0') self.incDBVersion() + return self.checkDBVersion() -class AddSceneAbsoluteNumbering(AddAbsoluteNumbering): - def test(self): - return self.checkDBVersion() >= 34 +# 33 -> 34 +class AddSceneAbsoluteNumbering(db.SchemaUpgrade): def execute(self): - backupDatabase(34) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column absolute_number and scene_absolute_number to scene_numbering") - self.addColumn("scene_numbering", "absolute_number", "NUMERIC", "0") - self.addColumn("scene_numbering", "scene_absolute_number", "NUMERIC", "0") + logger.log(u'Adding column absolute_number and scene_absolute_number to scene_numbering') + self.addColumn('scene_numbering', 'absolute_number', 'NUMERIC', '0') + self.addColumn('scene_numbering', 'scene_absolute_number', 'NUMERIC', '0') self.incDBVersion() + return self.checkDBVersion() -class AddAnimeBlacklistWhitelist(AddSceneAbsoluteNumbering): - - def test(self): - return self.checkDBVersion() >= 35 +# 34 -> 35 +class AddAnimeBlacklistWhitelist(db.SchemaUpgrade): def execute(self): - backupDatabase(35) + backup_database(self.checkDBVersion()) cl = [] - cl.append(["CREATE TABLE blacklist (show_id INTEGER, range TEXT, keyword TEXT)"]) - cl.append(["CREATE TABLE whitelist (show_id INTEGER, range TEXT, keyword TEXT)"]) + cl.append(['CREATE TABLE blacklist (show_id INTEGER, range TEXT, keyword TEXT)']) + cl.append(['CREATE TABLE whitelist (show_id INTEGER, range TEXT, keyword TEXT)']) self.connection.mass_action(cl) self.incDBVersion() + return self.checkDBVersion() -class AddSceneAbsoluteNumbering(AddAnimeBlacklistWhitelist): - def test(self): - return self.checkDBVersion() >= 36 +# 35 -> 36 +class AddSceneAbsoluteNumbering2(db.SchemaUpgrade): def execute(self): - backupDatabase(36) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column scene_absolute_number to tv_episodes") - self.addColumn("tv_episodes", "scene_absolute_number", "NUMERIC", "0") + logger.log(u'Adding column scene_absolute_number to tv_episodes') + self.addColumn('tv_episodes', 'scene_absolute_number', 'NUMERIC', '0') self.incDBVersion() + return self.checkDBVersion() -class AddXemRefresh(AddSceneAbsoluteNumbering): - def test(self): - return self.checkDBVersion() >= 37 +# 36 -> 37 +class AddXemRefresh(db.SchemaUpgrade): def execute(self): - backupDatabase(37) + backup_database(self.checkDBVersion()) - logger.log(u"Creating table xem_refresh") + logger.log(u'Creating table xem_refresh') self.connection.action( - "CREATE TABLE xem_refresh (indexer TEXT, indexer_id INTEGER PRIMARY KEY, last_refreshed INTEGER)") + 'CREATE TABLE xem_refresh (indexer TEXT, indexer_id INTEGER PRIMARY KEY, last_refreshed INTEGER)') self.incDBVersion() + return self.checkDBVersion() -class AddSceneToTvShows(AddXemRefresh): - def test(self): - return self.checkDBVersion() >= 38 +# 37 -> 38 +class AddSceneToTvShows(db.SchemaUpgrade): def execute(self): - backupDatabase(38) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column scene to tv_shows") - self.addColumn("tv_shows", "scene", "NUMERIC", "0") + logger.log(u'Adding column scene to tv_shows') + self.addColumn('tv_shows', 'scene', 'NUMERIC', '0') self.incDBVersion() + return self.checkDBVersion() -class AddIndexerMapping(AddSceneToTvShows): - def test(self): - return self.checkDBVersion() >= 39 +# 38 -> 39 +class AddIndexerMapping(db.SchemaUpgrade): def execute(self): - backupDatabase(39) + backup_database(self.checkDBVersion()) - if self.hasTable("indexer_mapping"): - self.connection.action("DROP TABLE indexer_mapping") + if self.hasTable('indexer_mapping'): + self.connection.action('DROP TABLE indexer_mapping') - logger.log(u"Adding table indexer_mapping") + logger.log(u'Adding table indexer_mapping') self.connection.action( - "CREATE TABLE indexer_mapping (indexer_id INTEGER, indexer NUMERIC, mindexer_id INTEGER, mindexer NUMERIC, PRIMARY KEY (indexer_id, indexer))") + 'CREATE TABLE indexer_mapping (indexer_id INTEGER, indexer NUMERIC, mindexer_id INTEGER, mindexer NUMERIC, PRIMARY KEY (indexer_id, indexer))') self.incDBVersion() + return self.checkDBVersion() -class AddVersionToTvEpisodes(AddIndexerMapping): - def test(self): - return self.checkDBVersion() >= 40 +# 39 -> 40 +class AddVersionToTvEpisodes(db.SchemaUpgrade): def execute(self): - backupDatabase(40) + backup_database(self.checkDBVersion()) - logger.log(u"Adding column version to tv_episodes and history") - self.addColumn("tv_episodes", "version", "NUMERIC", "-1") - self.addColumn("tv_episodes", "release_group", "TEXT", "") - self.addColumn("history", "version", "NUMERIC", "-1") + logger.log(u'Adding column version to tv_episodes and history') + self.addColumn('tv_episodes', 'version', 'NUMERIC', '-1') + self.addColumn('tv_episodes', 'release_group', 'TEXT', '') + self.addColumn('history', 'version', 'NUMERIC', '-1') self.incDBVersion() + return self.checkDBVersion() + + +# 40 -> 10000 +class BumpDatabaseVersion(db.SchemaUpgrade): + def execute(self): + backup_database(self.checkDBVersion()) + logger.log(u'Bumping database version') + + self.setDBVersion(10000) + return self.checkDBVersion() + + +# 41 -> 10001 +class Migrate41(db.SchemaUpgrade): + def execute(self): + backup_database(self.checkDBVersion()) + + logger.log(u'Bumping database version') + + self.setDBVersion(10001) + return self.checkDBVersion() + + +# 10000 -> 20000 +class SickGearDatabaseVersion(db.SchemaUpgrade): + def execute(self): + backup_database(self.checkDBVersion()) + + logger.log('Bumping database version to new SickGear standards') + + self.setDBVersion(20000) + return self.checkDBVersion() + + +# 10001 -> 10000 +class RemoveDefaultEpStatusFromTvShows(db.SchemaUpgrade): + def execute(self): + backup_database(self.checkDBVersion()) + + logger.log(u'Dropping column default_ep_status from tv_shows') + self.dropColumn('tv_shows', 'default_ep_status') + + self.setDBVersion(10000) + return self.checkDBVersion() diff --git a/sickbeard/db.py b/sickbeard/db.py index 8cf05545..5f9ee77f 100644 --- a/sickbeard/db.py +++ b/sickbeard/db.py @@ -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)) \ No newline at end of file diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index 1be5b37e..7f29e943 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -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) \ No newline at end of file + 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() diff --git a/sickbeard/notifiers/plex.py b/sickbeard/notifiers/plex.py index d04842b7..d6ab265b 100644 --- a/sickbeard/notifiers/plex.py +++ b/sickbeard/notifiers/plex.py @@ -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('
  • (.+\w)').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 diff --git a/sickbeard/providers/kat.py b/sickbeard/providers/kat.py index f1775e6c..eed0a1e0 100644 --- a/sickbeard/providers/kat.py +++ b/sickbeard/providers/kat.py @@ -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 diff --git a/sickbeard/providers/torrentday.py b/sickbeard/providers/torrentday.py index e119c7c1..29aef590 100644 --- a/sickbeard/providers/torrentday.py +++ b/sickbeard/providers/torrentday.py @@ -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): diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py index a50fe06f..888d9092 100644 --- a/sickbeard/show_name_helpers.py +++ b/sickbeard/show_name_helpers.py @@ -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 diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py index fc4dca28..681ab310 100644 --- a/sickbeard/tvcache.py +++ b/sickbeard/tvcache.py @@ -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 diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index b72c57cc..9d888825 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -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 += "
    \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 += "
    Restart SickGear to complete the restore." - else: - finalResult += "Restore FAILED" - else: - finalResult += "You need to select a backup file to restore!" - - finalResult += "
    \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': '' + ek.ek(os.path.dirname, cur_path) + os.sep + '' + ek.ek( + 'display_dir': '' + ek.ek(os.path.dirname, cur_path) + os.sep + '' + 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) diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py index 0f8aaa5e..528dae4a 100644 --- a/sickbeard/webserveInit.py +++ b/sickbeard/webserveInit.py @@ -136,6 +136,4 @@ class WebServer(threading.Thread): def shutDown(self): self.alive = False - if self.server: - self.server.stop() - self.io_loop.stop() \ No newline at end of file + self.io_loop.stop() \ No newline at end of file diff --git a/tests/db_tests.py b/tests/db_tests.py index 3d91536a..e8f85b22 100644 --- a/tests/db_tests.py +++ b/tests/db_tests.py @@ -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 "==================" diff --git a/tests/migration_tests.py b/tests/migration_tests.py new file mode 100644 index 00000000..068fbccf --- /dev/null +++ b/tests/migration_tests.py @@ -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) diff --git a/tests/test_lib.py b/tests/test_lib.py index 43d87eaa..de1eb62d 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -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(): diff --git a/updater.py b/updater.py deleted file mode 100644 index 07644a6e..00000000 --- a/updater.py +++ /dev/null @@ -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()
  • $show_names[$cur_indexer_id] ($ep_counts[$cur_indexer_id])
    '+season+'x'+episode+''+name+'
    ' + + '' + season + 'x' + episode + '' + name + '