Merge branch 'release/0.11.0'

This commit is contained in:
JackDandy 2016-01-10 22:30:34 +00:00
commit 2505cb469e
449 changed files with 16349 additions and 13602 deletions

17
.codeclimate.yml Normal file
View file

@ -0,0 +1,17 @@
engines:
eslint:
enabled: true
csslint:
enabled: true
pep8:
enabled: true
ratings:
paths:
- "**.py"
- "**.js"
- "**.css"
exclude_paths:
- lib/**/*
- tornado/**/*
- gui/slick/css/lib/**/*
- gui/slick/js/lib/**/*

View file

@ -1,7 +1,122 @@
### 0.11.0 (2016-01-10 22:30:00 UTC)
* Change to only refresh scene exception data for shows that need it
* Change reduce aggressive use of scene numbering that was overriding user preference where not needed
* Change set "Scene numbering" checkbox and add text to the label tip in third step of add "New Show" if scene numbers
are found for the selected show in the search results of the first step
* Change label text on edit show page to highlight when manual numbering and scene numbers are available
* Fix disabling "Scene numbering" of step three in add "New Show" was ignored when scene episode number mappings exist
* Fix don't use scene episode number mappings everywhere when "Scene numbering" is disabled for a show
* Fix width of legend underlining on the third step used to bring other display elements into alignment
* Change when downloading magnet or nzb files, verify the file in cache dir and then move to blackhole
* Fix small cosmetic issue to correctly display "full backlog" date
* Add search crawler exclusions
* Fix saving default show list group on add new show options page
* Remove legacy anime split home option from anime settings tab (new option located in general/interface tab)
* Remove "Manage Torrents"
* Update Beautiful Soup 4.3.2 to 4.4.0 (r390)
* Update dateutil library to 2.4.2 (083f666)
* Update chardet packages to 2.3.0 (26982c5)
* Update Hachoir library 1.3.3 to 1.3.4 (r1383)
* Change configure quiet option in Hachoir to suppress warnings (add ref:hacks.txt)
* Add parse media content to determine quality before making final assumptions during re-scan, update, pp
* Add a postprocess folder name validation
* Update Requests library to 2.7.0 (5d6d1bc)
* Update SimpleJSON library 3.7.3 to 3.8.0 (a37a9bd)
* Update Tornado Web Server 4.2 to 4.3.dev1 (1b6157d)
* Update isotope library 2.0.1 to 2.2.2
* Update change to suppress reporting of Tornado exception error 1 to updated package (ref:hacks.txt)
* Update fix for API response header for JSON content type and the return of JSONP data to updated package (ref:hacks.txt)
* Update TvDB API library 1.09 with changes up to (35732c9) and some pep8 and code cleanups
* Fix post processing season pack folders
* Fix saving torrent provider option "Seed until ratio" after recent refactor
* Change white text in light theme on Manage / Episode Status Management page to black for better readability
* Change displayShow page episode colours when a minimum quality is met with "End upgrade on first match"
* Add seed time per provider for torrent clients that support seed time per torrent, i.e. currently only uTorrent
* Remove seed time display for Transmission in config/Torrent Search page because the torrent client doesn't support it
* Add PreToMe torrent provider
* Add SceneTime torrent provider
* Change TtN provider to parse new layout
* Improve recognition of SD quality
* Fix halting in mid flow of Add Existing Show which resulted in failure to scan statuses and filesizes
* Change default de-referrer url to blank
* Change javascript urls in templates to allow proper caching
* Change downloads to prevent cache misfiring with "Result is not a valid torrent file"
* Add BitMeTV torrent provider
* Add Torrenting provider
* Add FunFile torrent provider
* Add TVChaosUK torrent provider
* Add HD-Space torrent provider
* Add Shazbat torrent provider
* Remove unnecessary call to indexers during nameparsing
* Change disable ToTV due to non-deletable yet reported hacker BTC inbox scam and also little to no new content listings
* Fix Episode View KeyError: 'state-title' failure for shows without a runtime
* Update py-unrar2 library 99.3 to 99.6 (2fe1e98)
* Fix py-unrar2 on unix to handle different date formats output by different unrar command line versions
* Fix Add and Edit show quality selection when Quality 'Custom' is used
* Fix add existing shows from folders that contain a plus char
* Fix post process issue where items in history were processed out of turn
* Change increase frequency of updating show data
* Remove Animenzb provider
* Change increase the scope and number of non release group text that is identified and removed
* Add general config setting to allow adding incomplete show data
* Change to throttle connection rate on thread initiation for adba library
* Change default manage episodes selector to Snatched episodes if items exist else Wanted on Episode Status Manage page
* Change snatched row colour on Episode Status Manage page to match colour used on the show details page
* Change replace trakt with libtrakt for API v2
* Change improve robustness of Trakt communications
* Change Trakt notification config to use PIN authentication with the service
* Add multiple Trakt account support to Config/Notifications/Social
* Add setting to Trakt notification to update collection with downloaded episode info
* Change trakt notifier logo
* Remove all other Trakt deprecated API V1 service features pending reconsideration
* Change increase show search capability when using plain text and also add TVDB id, IMDb id and IMDb url search
* Change improve existing show page and the handling when an attempt to add a show to an existing location
* Change consolidate Trakt Trending and Recommended views into an "Add From Trakt" view which defaults to trending
* Change Add from Trakt/"Shows:" with Anticipated, New Seasons, New Shows, Popular, Recommendations, and Trending views
* Change Add from Trakt/"Shows:" with Most Watched, Played, and Collected during the last month and year on Trakt
* Change add season info to "Show: Trakt New Seasons" view on the Add from Trakt page
* Change increase number of displayed Trakt shows to 100
* Add genres and rating to all Trakt shows
* Add AniDb Random and Hot to Add Show page
* Add IMDb Popular to Add Show page
* Add version to anime renaming pattern
* Add Code Climate configuration files
* Change move init-scripts to single folder
* Change sickbeard variables to sickgear variables in init-scripts
* Change improve the use of multiple plex servers
* Change move JS code out of home template and into dedicated file
* Change remove branch from window title
* Change move JS code out of inc_top template and into dedicated file
* Change cleanup torrent providers
* Change utilise tvdbid for searching usenet providers
* Add setting to provider BTN to Reject Blu-ray M2TS releases
* Remove jsonrpclib library
* Change consolidate global and per show ignore and require words functions
* Change "Require word" title and notes on Config Search page to properly describe its functional logic
* Add regular expression capability to ignore and require words by starting wordlist with "regex:"
* Add list shows with custom ignore and require words under the global counterparts on the Search Settings page
* Fix failure to search for more than one selected wanted episode
* Add notice for users with Python 2.7.8 or below to update to latest Python
* Change position of parsed qualities to the start of log lines
* Change to always display branch and commit hash on 'Help & Info' page
* Add option to create season search exceptions from editShow page
* Change sab to use requests library
* Add "View Changes" to tools menu
* Change disable connection attempts and remove UI references to the TVRage info source
* Change to simplify xem id fetching
* Fix issue on Add Existing Shows page where shows were listed that should not have been
* Change get_size helper to also handle files
* Change improve handling of a bad email notify setting
* Fix provider MTV download URL
* Change give provider OMGWTFNZBS more time to respond
* Change file browser to permit manually entering a path
### 0.10.0 (2015-08-06 11:05:00 UTC)
* Remove EZRSS provider
* Update Tornado webserver to 4.2 (fdfaf3d)
* Update Tornado Web Server to 4.2 (fdfaf3d)
* Update change to suppress reporting of Tornado exception error 1 to updated package (ref:hacks.txt)
* Update fix for API response header for JSON content type and the return of JSONP data to updated package (ref:hacks.txt)
* Update Requests library 2.6.2 to 2.7.0 (8b5e457)
@ -117,7 +232,7 @@
### 0.9.0 (2015-05-18 14:33:00 UTC)
* Update Tornado webserver to 4.2.dev1 (609dbb9)
* Update Tornado Web Server to 4.2.dev1 (609dbb9)
* Update change to suppress reporting of Tornado exception error 1 to updated package as listed in hacks.txt
* Update fix for API response header for JSON content type and the return of JSONP data to updated package as listed in hacks.txt
* Change network names to only display on top line of Day by Day layout on Episode View
@ -234,7 +349,7 @@
* Hide year, runtime, genre tags, country flag, or status if lacking valid data to display
* Remove redundant CSS color use (all browsers treat 3 identical digits as 6, except for possibly in gradients)
* Remove whitespace and semi-colon redundancy from CSS shedding 4.5kb
* Add show names to items listed during startup in the loading from database phase
* Add show names to items listed during startup in the loading from database phase
* Add "Enable IMDb info" option to config/General/Interface
* Change to not display IMDb info on UI when "Enable IMDb info" is disabled
* Change genre tags on displayShow page to link to IMDb instead of Trakt
@ -321,7 +436,7 @@
* Change anime release groups to in memory storage for lowered latency
* Change adjust menu delay and hover styling
* Fix provider list color
* Add handling of exceptional case with missing network name (NoneType) in Episode View
* Add handling of exceptional case with missing network name (NoneType) in Episode View
* Fix black and white list initialization on new show creation
* Add select all and clear all buttons to testRename template
* Fix displayShow topmenu variable to point to a valid menu item
@ -477,7 +592,7 @@
* 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
* 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
@ -631,7 +746,7 @@
* Add return code from hardlinking error to log
* Fix ABD regex for certain filenames
* Change miscellaneous UI fixes
* Update Tornado webserver to 4.1dev1 and add the certifi lib dependency
* Update Tornado Web Server to 4.1dev1 and add the certifi lib dependency
* Fix trending shows page from loading full size poster images
* Add "Archive on first match" to Manage, Mass Update, Edit Selected page
* Fix searching IPTorrentsProvider

View file

@ -1,7 +1,10 @@
Libs with customisations...
/tornado
/lib/cachecontrol/caches/file_cache.py
/lib/hachoir_core/config.py
/lib/pynma/pynma.py
/lib/requests/packages/urllib3/connectionpool.py
/lib/requests/packages/urllib3/util/ssl_.py
/lib/cachecontrol/caches/file_cache.py
/lib/pynma/pynma.py
/tornado
/lib/unrar2/unix.py
/lib/tvdb/tvdb_api.py

View file

@ -340,7 +340,7 @@ class SickGear(object):
logger.ERROR)
if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon:
logger.log(u'Launching browser and exiting', logger.ERROR)
sickbeard.launchBrowser(self.startPort)
sickbeard.launch_browser(self.startPort)
os._exit(1)
# Check if we need to perform a restore first
@ -377,7 +377,7 @@ class SickGear(object):
# Launch browser
if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon):
sickbeard.launchBrowser(self.startPort)
sickbeard.launch_browser(self.startPort)
# main loop
while True:
@ -488,7 +488,7 @@ class SickGear(object):
sickbeard.halt()
# save all shows to DB
sickbeard.saveAll()
sickbeard.save_all()
# shutdown web server
if self.webserver:

View file

@ -20,7 +20,7 @@ inc_top.tmpl
}
.browserDialog.busy .ui-dialog-buttonpane{
background:url("../images/loading.gif") 10px 50% no-repeat !important
background:url("../images/loading32-dark.gif") 10px 50% no-repeat !important
}
.ui-progressbar .ui-progressbar-overlay{
@ -61,6 +61,8 @@ inc_top.tmpl
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default{
background:#3d3d3d;
color:#fff;
border:1px solid #111
}
@ -326,15 +328,15 @@ ul#rootDirStaticList li{
}
/* =======================================================================
home_trendingShows.tmpl
home_browseShows.tmpl
========================================================================== */
.traktContainer{
.browse-container{
background-color:#333;
border:1px solid #111
}
.trakt-image{
.browse-image{
border-bottom:1px solid #111
}
@ -526,7 +528,16 @@ h2.day, h2.network{
.carousel-indicators .active{
background:#8DBEEE;
border-color:#8DBEEE
}
}
/* =======================================================================
viewchanges.tmpl
========================================================================== */
#changes{
color:rgb(255,255,255);
background-color:rgb(61,61,61);
border:1px solid rgb(17,17,17)
}
/* =======================================================================
config*.tmpl
@ -626,6 +637,14 @@ div.metadataDiv .disabled{
background:url("../images/warning16.png") no-repeat right 5px center #fff
}
.solid-border{
border:1px solid #555
}
.solid-border-top{
border-top:1px solid #555
}
/* =======================================================================
manage*.tmpl
========================================================================== */
@ -655,6 +674,7 @@ span.path{
background-color:#333
}
#addRootDirTable td label .filepath.red-text,
.red-text{
color:#d33
}
@ -1159,12 +1179,14 @@ input sizing (for config pages)
========================================================================== */
#pickShow optgroup,
#showfilter optgroup,
#editAProvider optgroup{
color:#eee;
background-color:rgb(51, 51, 51)
}
#pickShow optgroup option,
#showfilter optgroup option,
#editAProvider optgroup option{
color:#222;
background-color:#fff
@ -1180,7 +1202,7 @@ browser.css
#fileBrowserDialog ul li a:hover{
color:#09a2ff;
background:none
background: rgb(61, 61, 61) none
}
.ui-menu .ui-menu-item{
@ -1214,6 +1236,10 @@ div.stepsguide .step p{
color:#646464
}
#newShowPortal #addShowForm .stepsguide .disabledstep:hover > .smalltext{
color:#ccc;
}
div.stepsguide .disabledstep p{
border-color:#1178B3
}
@ -1444,4 +1470,4 @@ jquery.confirm.css
#confirmBox .red:hover{
background-color:#A13331
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -16,19 +16,19 @@ inc_top.tmpl
}
.browserDialog.busy .ui-dialog-buttonpane{
background:url("../images/loading.gif") 10px 50% no-repeat !important
background:url("../images/loading32.gif") 10px 50% no-repeat !important
}
.ui-progressbar .ui-progressbar-overlay{
background:url("../css/lib/images/animated-overlay.gif")
}
.ui-dialog,
.ui-dialog,
.ui-dialog-buttonpane{
background:#eceadf url("../css/lib/images/ui-bg_fine-grain_10_eceadf_60x60.png") 50% 50% repeat !important
}
.ui-accordion-content,
.ui-accordion-content,
.ui-tabs-panel{
background:#ededed !important;
background-image:none !important
@ -50,41 +50,41 @@ inc_top.tmpl
background:#fff url("../css/lib/images/ui-bg_flat_0_ffffff_40x100.png") 50% 50% repeat-x
}
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default{
background:#fff;
border:1px solid #CCC
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus{
background:#fff
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active{
background:#F7F7F7
}
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-widget-header .ui-state-highlight{
background:#fbf9ee url("../css/lib/images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x
}
.ui-state-error,
.ui-widget-content .ui-state-error,
.ui-state-error,
.ui-widget-content .ui-state-error,
.ui-widget-header .ui-state-error{
background:#fef1ec url("../css/lib/images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x
}
.ui-icon,
.ui-icon,
.ui-widget-content .ui-icon{
background-image:url("../css/lib/images/ui-icons_222222_256x240.png")
}
@ -97,7 +97,7 @@ inc_top.tmpl
background-image:url("../css/lib/images/ui-icons_8c291d_256x240.png")
}
.ui-state-hover .ui-icon,
.ui-state-hover .ui-icon,
.ui-state-focus .ui-icon{
background-image:url("../css/lib/images/ui-icons_222222_256x240.png")
}
@ -110,7 +110,7 @@ inc_top.tmpl
background-image:url("../css/lib/images/ui-icons_2e83ff_256x240.png")
}
.ui-state-error .ui-icon,
.ui-state-error .ui-icon,
.ui-state-error-text .ui-icon{
background-image:url("../css/lib/images/ui-icons_cd0a0a_256x240.png")
}
@ -327,15 +327,15 @@ ul#rootDirStaticList li{
}
/* =======================================================================
home_trendingShows.tmpl
home_browseShows.tmpl
========================================================================== */
.traktContainer{
.browse-container{
background-color:#DFDACF;
border:1px solid #111
}
.trakt-image{
.browse-image{
border-bottom:1px solid #111
}
@ -345,7 +345,7 @@ home_postprocess.tmpl
/* =======================================================================
displayShow.tmpl
displayShow.tmpl
========================================================================== */
tr.seasonheader{
@ -508,7 +508,16 @@ h2.day, h2.network{
.carousel-indicators .active{
background:#C7DB40;
border-color:#C7DB40
}
}
/* =======================================================================
viewchanges.tmpl
========================================================================== */
#changes{
color:rgb(51,51,51);
background-color:rgb(245,245,245);
border:1px solid rgb(204,204,204)
}
/* =======================================================================
config*.tmpl
@ -601,6 +610,14 @@ div.metadataDiv .disabled{
background:url("../images/warning16.png") no-repeat right 5px center #fff
}
.solid-border{
border:1px solid #ccc
}
.solid-border-top{
border-top:1px solid #ccc
}
/* =======================================================================
manage*.tmpl
========================================================================== */
@ -610,7 +627,7 @@ manage*.tmpl
}
a.whitelink{
color:#fff
color:#000
}
/* =======================================================================
@ -630,6 +647,7 @@ span.path{
background-color:#f5f1e4
}
#addRootDirTable td label .filepath.red-text,
.red-text{
color:#d33
}
@ -1125,12 +1143,14 @@ input sizing (for config pages)
========================================================================== */
#pickShow optgroup,
#showfilter optgroup,
#editAProvider optgroup{
color:#eee;
background-color:#888
}
#pickShow optgroup option,
#showfilter optgroup option,
#editAProvider optgroup option{
color:#222;
background-color:#fff
@ -1142,7 +1162,7 @@ browser.css
#fileBrowserDialog ul li a:hover{
color:#00f;
background:none
background: rgb(220, 220, 220) none
}
.ui-menu .ui-menu-item{
@ -1180,6 +1200,10 @@ div.stepsguide .disabledstep p{
border-color:#8a775e
}
#newShowPortal #addShowForm .stepsguide .disabledstep:hover > .smalltext{
color:#8a775e;
}
div.formpaginate .prev, div.formpaginate .next{
color:#fff;
background:#57442b
@ -1378,4 +1402,4 @@ jquery.confirm.css
#confirmBox .red:hover{
background-color:#A13331
}
}

View file

@ -175,7 +175,7 @@ inc_top.tmpl
background:url("../css/lib/images/animated-overlay.gif")
}
.ui-dialog,
.ui-dialog,
.ui-dialog-buttonpane{
background:#eceadf url("../css/lib/images/ui-bg_fine-grain_10_eceadf_60x60.png") 50% 50% repeat !important
}
@ -921,7 +921,7 @@ home_newShow.tmpl
#newShowPortal,
fieldset.sectionwrap,
div.formpaginate{
width:801px
width:831px
}
#addShowForm{
@ -1054,25 +1054,25 @@ ul#rootDirStaticList li input[type="checkbox"]{
}
/* =======================================================================
home_trendingShows.tmpl
home_browseShows.tmpl
========================================================================== */
.traktShowTitleIcons{
.browse-add-show-holder{
float:right;
padding-right:4px;
padding-bottom:4px
}
.traktContainer p{
.browse-container p{
padding-top:2px
}
.traktContainer p img{
.browse-container p img{
position:relative;
top:-2px
}
.traktContainer p, .traktContainer i{
.browse-container p, .browse-container i{
white-space:nowrap;
font-size:12px;
overflow:hidden;
@ -1080,15 +1080,16 @@ home_trendingShows.tmpl
margin:0
}
.traktContainer{
margin:12px;
.browse-container{
margin:12px 12px 12px 0;
width:188px;
background-color:#DFDACF;
border:1px solid #111;
border-radius:6px
}
.trakt-image{
.browse-image{
display:block;
overflow:hidden;
height:273px;
width:186px;
@ -1666,6 +1667,10 @@ td.col-search{
padding:15px 0 0
}
#addShowForm #editShow.stepDiv span.component-desc{
width:639px
}
/* =======================================================================
episodeView.tmpl
========================================================================== */
@ -2098,6 +2103,36 @@ td.col-cache{
width:20px
}
/* =======================================================================
viewchanges.tmpl
========================================================================== */
#changes{
display:block;
padding:9.5px;
border-radius:4px 4px 4px 4px;
font:12px/13px "Open Sans",verdana,sans-serif
}
#changes .release{
margin:15px 5px 6px 0;
padding-bottom:3px;
border-bottom:1px solid gray
}
#changes .ver{font:16px/17px "Open Sans",verdana,sans-serif;margin-right:0.2em;}
#changes .old{padding-top:15px}
#changes div{margin:0 0 8px}
#changes .btn-text{width:5em;margin-right:0.2em;float:left}
#changes .change-text{display:block;margin-left:5.5em;padding-top:2px}
.change-add{background-color:rgb(63,127,0)}
.change-change{background-color:rgb(91,153,13)}
.change-fix{background-color:rgb(38,114,182)}
.change-port{background-color:rgb(102,102,102)}
.change-remove{}
.change-update{background-color:rgb(98,25,147)}
/* =======================================================================
config*.tmpl
@ -2524,6 +2559,32 @@ div.metadataDiv .disabled{
margin:6px 4px 0 0
}
#trakt-collection th,#trakt-collection td{
padding:3px 5px
}
#trakt-collection .col-1{
text-align:left
}
#trakt-collection th,#trakt-collection td.opt{
text-align:center
}
#trakt-collection .col-1{
width:192px
}
#config #trakt-collection input{
float:none;
margin:0;
vertical-align:middle
}
#config .trakt.component-desc{
margin-left:0
}
/* =======================================================================
manage*.tmpl
========================================================================== */
@ -2625,6 +2686,7 @@ span.path{
line-height:18px
}
span.btn-text,
span.quality{
font:12px/13px "Open Sans", verdana, sans-serif;
background-image:-webkit-linear-gradient(top, rgba(255, 255, 255, 0.08),rgba(255, 255, 255, 0) 50%,rgba(0, 0, 0, 0) 50%,rgba(0, 0, 0, 0.25));
@ -2970,6 +3032,10 @@ fieldset[disabled] .navbar-default .btn-link:focus{
display:inline
}
#fileBrowserDialog .form-control{background-color:#f5f1e4}
#fileBrowserDialog .form-control:active,
#fileBrowserDialog .form-control:hover{background-color:#ffffca}
.btn{
display:inline-block;
*display:inline;
@ -3486,7 +3552,7 @@ div.stepsguide{
div.stepsguide .step{
float:left;
width:267px;
width:277px;
font:bold 24px Arial
}
@ -3880,7 +3946,7 @@ jquery.confirm.css
box-shadow:inset 0 1px rgba(255, 255, 255, 0.1),inset 0 -1px 3px rgba(0, 0, 0, 0.3),inset 0 0 0 1px rgba(255, 255, 255, 0.08),0 1px 2px rgba(0, 0, 0, 0.15)
}
#confirmBox .button:last-child{
#confirmBox .button:last-child{
margin-right:0
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

0
gui/slick/images/providers/freshontv.png Executable file → Normal file
View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,002 B

View file

@ -8,8 +8,8 @@
sbRoot = "$sbRoot";
//-->
</script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/apibuilder.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/apibuilder.js?v=$sbPID"></script>
<style type="text/css">
<!--

View file

@ -8,36 +8,23 @@
#set global $sbPath = '..'
#set global $topmenu = 'config'
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
##
#if $varExists('header')
<h1 class='header'>$header</h1>
#else
<h1 class='title'>$title</h1>
#end if
##set cpu_usage = $psutil.cpu_percent()
##set ram = $psutil.phymem_usage()
##set ram_total = $ram.total / 2**20
##set ram_used = $ram.used / 2**20
##set ram_free = $ram.free / 2**20
##set ram_percent_used = $ram.percent
##set disk = $psutil.disk_usage('/')
##set disk_total = $disk.total / 2**30
##set disk_used = $disk.used / 2**30
##set disk_free = $disk.free / 2**30
##set disk_percent_used = $disk.percent
##
<div id="config-content">
<table class="infoTable" cellspacing="1" border="0" cellpadding="0" width="100%">
<tr>
<td class="infoTableHeader">Version: </td>
<td class="infoTableCell">
#if $sickbeard.VERSION_NOTIFY
BRANCH: ($sickbeard.BRANCH) / COMMIT: ($sickbeard.CUR_COMMIT_HASH) <!-- &ndash; build.date //--><br />
#else
You don't have version checking turned on, see "Check software updates" in Config > General.<br />
BRANCH: #echo $sickbeard.BRANCH or 'UNKNOWN'# / COMMIT: #echo $sickbeard.CUR_COMMIT_HASH or 'UNKNOWN'#<br />
<em class="red-text">This is BETA software</em><br />
#if not $sickbeard.VERSION_NOTIFY:
You don't have version checking turned on, see "Check software updates" in Config > General.
#end if
<em class="red-text">This is BETA software.</em>
</td>
</tr>
<tr><td class="infoTableHeader">Config file:</td><td class="infoTableCell">$sickbeard.CONFIG_FILE</td></tr>

View file

@ -8,7 +8,7 @@
##
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?v=$sbPID"></script>
#if $varExists('header')
<h1 class="header">$header</h1>
@ -36,16 +36,6 @@
</div>
<fieldset class="component-group-list">
<div class="field-pair">
<label for="split_home">
<span class="component-title">Split show lists</span>
<span class="component-desc">
<input type="checkbox" class="enabler" name="split_home" id="split_home" #if $sickbeard.ANIME_SPLIT_HOME then 'checked="checked"' else ""# />
<p>separate anime from other shows on the home page</p>
</span>
</label>
</div>
<div class="field-pair">
<label for="anime_treat_as_hdtv">
<span class="component-title">Quality control</span>

View file

@ -32,8 +32,8 @@
#set $indexer = $sickbeard.INDEXER_DEFAULT
#end if
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?v=$sbPID"></script>
<div id="config">
<div id="config-content">
@ -72,7 +72,7 @@
<span class="component-title">Update shows on startup</span>
<span class="component-desc">
<input type="checkbox" name="update_shows_on_start" id="update_shows_on_start"#echo ('', $checked)[$sickbeard.UPDATE_SHOWS_ON_START]#>
<p>with show data; episode plot, images, air and end dates, etc. Disable for a quicker startup. Show data is scheduled to update during hour <span class="show_update_hour_value">$sickbeard.SHOW_UPDATE_HOUR</span>.</p>
<p>with show data; episode plot, images, air and end dates, etc. Disable for a quicker startup. Show data is scheduled to update during hour <span class="show_update_hour_value">$sickbeard.SHOW_UPDATE_HOUR</span></p>
</span>
</label>
</div>
@ -86,7 +86,19 @@
</span>
</label>
</div>
#if hasattr($sickbeard, 'ALLOW_INCOMPLETE_SHOWDATA')
<div class="field-pair">
<label for="allow_incomplete_showdata">
<span class="component-title">Allow incomplete show data</span>
<span class="component-desc">
<input type="checkbox" name="allow_incomplete_showdata" id="allow_incomplete_showdata"#echo ('', $checked)[$sickbeard.ALLOW_INCOMPLETE_SHOWDATA]#>
<p>add partial show data for future updates to complete</p>
</span>
</label>
</div>
#end if
<div class="field-pair">
<span class="component-title">Send to trash for actions</span>
<span class="component-desc">
@ -110,22 +122,22 @@
</span>
</label>
</div>
#if 1 < $len($indexers)
<div class="field-pair">
<label for="indexer_default">
<span class="component-title">Use initial indexer set to</span>
<span class="component-desc">
<select id="indexer_default" name="indexer_default" class="form-control input-sm">
<option value="0"#echo ('', $selected)[0 == $indexer]#>All Indexers</option>
#for $indexer in $sickbeard.indexerApi().indexers
#for $indexer in $indexers
<option value="$indexer"#echo ('', $selected)[$indexer == $sickbeard.INDEXER_DEFAULT]#>$sickbeard.indexerApi().indexers[$indexer]</option>
#end for
#end for
</select>
<span>as the default selection when adding new shows</span>
</span>
</label>
</div>
#end if
<div class="field-pair">
<label for="indexer_timeout">
<span class="component-title">Timeout show indexer at</span>
@ -624,7 +636,7 @@
<span class="component-title">Use proxy for indexers</span>
<span class="component-desc">
<input type="checkbox" name="proxy_indexers" id="proxy_indexers"#echo ('', $checked)[True == $sickbeard.PROXY_INDEXERS]#>
<p>use proxy host for connecting to indexers (thetvdb, tvrage)</p>
<p>use proxy host for TV info source connections</p>
</span>
</label>
</div>

View file

@ -1,5 +1,7 @@
#import base64
#import sickbeard
#import re
#from lib.libtrakt import TraktAPI
#from sickbeard.helpers import anon_url, starify
##
#set global $title = 'Config - Notifications'
@ -9,12 +11,12 @@
##
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/configNotifications.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/configNotifications.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?v=$sbPID"></script>
#if $varExists('header')
#if $varExists('header')
<h1 class="header">$header</h1>
#else
#else
<h1 class="title">$title</h1>
#end if
@ -154,7 +156,7 @@
</div><!-- /xbmc component-group //-->
<div class="component-group">
<div class="component-group">
<div class="component-group-desc">
<img class="notifier-icon" src="$sbRoot/images/notifiers/kodi.png" alt="" title="Kodi" />
<h3><a href="<%= anon_url('http://kodi.tv/') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">Kodi</a></h3>
@ -274,7 +276,7 @@
</div><!-- /content_use_kodi //-->
</fieldset>
</div><!-- /kodi component-group //-->
<div class="component-group">
<div class="component-group-desc">
<img class="notifier-icon" src="$sbRoot/images/notifiers/plex.png" alt="" title="Plex Media Server" />
@ -1465,7 +1467,7 @@
<div class="component-group-desc">
<img class="notifier-icon" src="$sbRoot/images/notifiers/trakt.png" alt="" title="Trakt"/>
<h3><a href="<%= anon_url('http://trakt.tv/') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">Trakt</a></h3>
<p>trakt helps keep a record of what TV shows and movies you are watching. Based on your favorites, trakt recommends additional shows and movies you'll enjoy!</p>
<p>Trakt can keep a record of what TV shows you are watching and recommend additional shows based on your show data.</p>
</div>
<fieldset class="component-group-list">
<div class="field-pair">
@ -1473,42 +1475,85 @@
<span class="component-title">Enable</span>
<span class="component-desc">
<input type="checkbox" class="enabler" name="use_trakt" id="use_trakt" #if $sickbeard.USE_TRAKT then 'checked="checked"' else ''# />
<p>should SickGear send Trakt.tv notifications ?</p>
<p>should SickGear use Trakt.tv ?</p>
</span>
</label>
</div>
<div id="content_use_trakt">
<div class="field-pair">
<label for="trakt_username">
<span class="component-title">Trakt username</span>
<input type="text" name="trakt_username" id="trakt_username" value="$sickbeard.TRAKT_USERNAME" class="form-control input-sm input250" />
<label for="trakt_accounts">
<span class="component-title">Trakt account (status):</span>
<span class="component-desc">
<select name="trakt_accounts" id="trakt_accounts" class="pull-left form-control input-sm">
<option value="new" selected="selected">Add account</option>
#set $trakt_accounts = $sickbeard.TRAKT_ACCOUNTS
#for $void, $account in $trakt_accounts.items()
<option value="$account.account_id">$account.account_id - $account.name #if $account.active then '(ok)' else '(inactive)'#</option>
#end for
</select>
<input type="button" class="btn" value="Delete" id="trakt-delete" disabled="disabled" />
</span>
</label>
<label>
<span class="component-title">&nbsp;</span>
<span class="component-desc">username of your Trakt account.</span>
<label for="trakt_pin">
<span class="component-title">Trakt PIN:</span>
<span class="component-desc">
<input type="text" name="trakt_pin" id="trakt_pin" value="" class="form-control input-sm input250" />
<input type="button" class="btn" value="Connect" id="trakt-authenticate" />
<div class="clear-left"><p>get an active PIN using: <a href="<%= anon_url(sickbeard.TRAKT_PIN_URL) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;"><b>$sickbeard.TRAKT_PIN_URL</b></a>
<br />tip: easily add accounts by using the link in other browsers "signed in" to Trakt
</p></div>
</span>
</label>
<div class="testNotification" id="trakt-authentication-result"></div>
</div>
<div class="field-pair">
<label for="trakt_password">
<span class="component-title">Trakt password</span>
<input type="password" name="trakt_password" id="trakt_password" value="#echo '*' * len($sickbeard.TRAKT_PASSWORD)#" class="form-control input-sm input250" />
</label>
<label>
<span class="component-title">&nbsp;</span>
<span class="component-desc">password of your Trakt account.</span>
</label>
</div>
<div class="field-pair">
<label for="trakt_api">
<span class="component-title">Trakt API key:</span>
<input type="text" name="trakt_api" id="trakt_api" value="<%= starify(sickbeard.TRAKT_API) %>" class="form-control input-sm input250" />
</label>
<label>
<span class="component-title">&nbsp;</span>
<span class="component-desc">get your key at: <a href="<%= anon_url('http://trakt.tv/settings/api') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;"><b>http://trakt.tv/settings/api</b></a></span>
</label>
<span class="trakt component-desc" style="width:100%">
#set num_accounts = len($trakt_accounts)
#set $num_columns = (1, num_accounts)[1 < num_accounts]
<table id="trakt-collection" class="solid-border" cellpadding="0" cellspacing="0" border="0">
<thead>
<tr>
<th class="col-1" style="font-size:12px;font-weight:normal" rowspan="2"><i>Update multiple accounts with downloaded episode info</i></th>
<th colspan="$num_columns">#echo not len($trakt_accounts) and '<i>Connect New Pin</i>' or 1 < len($trakt_accounts) and 'Trakt accounts' or 'Account'#</th>
</tr>
<tr>
#if not len($trakt_accounts)
<th>..</th>
#end if
#for $void, $account in $trakt_accounts.items()
<th class="tid-$account.account_id">$account.name#if $account.active then '' else '<br />(inactive)'#</th>
#end for
</tr>
</thead>
<tbody>
#if not $root_dirs:
#set $root_dirs = [{'root_def': False, 'loc': 'all folders. Multiple parent folders will appear here.', 'b64': ''}]
#end if
#for $root_info in $root_dirs:
<tr class="solid-border-top">
<td class="col-1"><span style="font-size:13px;font-weight:bold" data-loc="$root_info['b64']">Update collection</span></td>
#if not len($trakt_accounts)
<td class="opt">..</td>
#end if
#for $void, $account in $trakt_accounts.items()
#set $cur_selected = ('', ' checked="checked"')[$root_info['loc'] in $sickbeard.TRAKT_UPDATE_COLLECTION.get($account.account_id, '')]
#set $id_loc = "update_trakt_%s_%s" % ($account.account_id, $root_info['b64'])
<td class="opt">
<input type="checkbox" id="$id_loc" name="$id_loc"$cur_selected />
</td>
#end for
</tr>
<tr>
<td colspan="${1 + $num_columns}">for #if $root_info['root_def'] then '*' else ''#$root_info['loc']</td>
</tr>
#end for
</tbody>
</table>
</span>
</div>
<!--
<div class="field-pair">
<label for="trakt_default_indexer">
<span class="component-title">Default indexer:</span>
@ -1582,8 +1627,7 @@
</label>
</div>
</div>
<div class="testNotification" id="testTrakt-result">Click below to test.</div>
<input type="button" class="btn" value="Test Trakt" id="testTrakt" />
-->
<input type="submit" class="btn config_submitter" value="Save Changes" />
</div><!-- /content_use_trakt //-->
</fieldset>
@ -1743,4 +1787,4 @@
//-->
</script>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -14,12 +14,12 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/configPostProcessing.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/configPostProcessing.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?v=$sbPID"></script>
#if $varExists('header')
#if $varExists('header')
<h1 class="header">$header</h1>
#else
#else
<h1 class="title">$title</h1>
#end if
@ -1073,6 +1073,11 @@
<td>%RT</td>
<td>PROPER</td>
</tr>
<tr class="even">
<td class="align-right"><b>Version:</b></td>
<td>%V</td>
<td>2</td>
</tr>
</tbody>
</table>
<br />

View file

@ -11,14 +11,14 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
#if $varExists('header')
#if $varExists('header')
<h1 class="header">$header</h1>
#else
#else
<h1 class="title">$title</h1>
#end if
<script type="text/javascript" src="$sbRoot/js/configProviders.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/configProviders.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?v=$sbPID"></script>
#set $methods_notused = []
#if not $sickbeard.USE_NZBS
@ -281,7 +281,7 @@
<span class="component-desc">
#set $field_name = curNzbProvider.get_id() + '_api_key'
<input type="text" name="$field_name" value="<%= starify(curNzbProvider.api_key) %>" class="form-control input-sm input350" />
#if callable(getattr(curNzbProvider, 'ui_string'))
#if callable(getattr(curNzbProvider, 'ui_string', None))
<div class="clear-left"><p>${curNzbProvider.ui_string($field_name)}</p></div>
#end if
</span>
@ -347,6 +347,17 @@
##
#for $curTorrentProvider in [$curProvider for $curProvider in $sickbeard.providers.sortedProviderList() if $curProvider.providerType == $GenericProvider.TORRENT]:
<div class="providerDiv" id="${curTorrentProvider.get_id()}Div">
#if callable(getattr(curTorrentProvider, 'ui_string', None))
#set $field_name = curTorrentProvider.get_id() + '_tip'
#set $tip_text = curTorrentProvider.ui_string($field_name)
#if $tip_text
<div class="field-pair">
<span class="component-desc" style="margin:0;width:100%">
<div class="clear-left"><p class="grey-text"><span class="red-text">Important! ${curTorrentProvider.name}</span> $tip_text</p></div>
</span>
</div>
#end if
#end if
#if $hasattr($curTorrentProvider, 'api_key'):
<div class="field-pair">
<label for="${curTorrentProvider.get_id()}_api_key">
@ -360,9 +371,13 @@
#if $hasattr($curTorrentProvider, 'digest'):
<div class="field-pair">
<label for="${curTorrentProvider.get_id()}_digest">
<span class="component-title">Digest:</span>
<span class="component-title">Cookies:</span>
<span class="component-desc">
<input type="text" name="${curTorrentProvider.get_id()}_digest" id="${curTorrentProvider.get_id()}_digest" value="$curTorrentProvider.digest" class="form-control input-sm input350" />
#set $field_name = curTorrentProvider.get_id() + '_digest'
<input type="text" name="$field_name" id="$field_name" value="<%= starify(curTorrentProvider.digest) %>" class="form-control input-sm input350" />
#if callable(getattr(curTorrentProvider, 'ui_string', None))
<div class="clear-left"><p>${curTorrentProvider.ui_string($field_name)}</p></div>
#end if
</span>
</label>
</div>
@ -392,7 +407,7 @@
<label for="${curTorrentProvider.get_id()}_password">
<span class="component-title">Password:</span>
<span class="component-desc">
<input type="password" name="${curTorrentProvider.get_id()}_password" id="${curTorrentProvider.get_id()}_password" value="#echo '*' * len($curTorrentProvider.password)#" class="form-control input-sm input350" />
<input type="password" name="${curTorrentProvider.get_id()}_password" id="${curTorrentProvider.get_id()}_password" value="#echo $curTorrentProvider.password and '*' * len($curTorrentProvider.password) or ''#" class="form-control input-sm input350" />
</span>
</label>
</div>
@ -408,14 +423,28 @@
</div>
#end if
#if $hasattr($curTorrentProvider, '_seed_ratio') and 'blackhole' != $sickbeard.TORRENT_METHOD:
#set $torrent_method_text = {'blackhole': 'Black hole', 'utorrent': 'uTorrent', 'transmission': 'Transmission', 'deluge': 'Deluge', 'download_station': 'Synology DS', 'rtorrent': 'rTorrent'}
#set $torrent_method_text = {'utorrent': 'uTorrent', 'transmission': 'Transmission', 'deluge': 'Deluge', 'download_station': 'Synology DS', 'rtorrent': 'rTorrent'}
<div class="field-pair">
<label for="${curTorrentProvider.get_id()}_ratio">
<span class="component-title" id="${curTorrentProvider.get_id()}_ratio_desc">Seed until ratio (the goal)</span>
<span class="component-desc">
<input type="number" step="0.1" name="${curTorrentProvider.get_id()}_ratio" id="${curTorrentProvider.get_id()}_ratio" value="$curTorrentProvider._seed_ratio" class="form-control input-sm input75" />
<p>this ratio is requested of each download sent to $torrent_method_text[$sickbeard.TORRENT_METHOD]</p>
<div class="clear-left"><p>(set -1 to seed forever, or leave blank for the $torrent_method_text[$sickbeard.TORRENT_METHOD] default)</p></div>
<input type="number" name="${curTorrentProvider.get_id()}_ratio" id="${curTorrentProvider.get_id()}_ratio" value="$curTorrentProvider._seed_ratio" class="form-control input-sm input75" />
<p>this ratio is requested of each item sent to $torrent_method_text[$sickbeard.TORRENT_METHOD]</p>
<div class="clear-left"><p>(#if 'Transmission' in $torrent_method_text[$sickbeard.TORRENT_METHOD]#set -1 to seed forever, or #end if#leave blank for the $torrent_method_text[$sickbeard.TORRENT_METHOD] setting)</p></div>
</span>
</label>
</div>
#end if
#if $hasattr($curTorrentProvider, 'seed_time') and 'utorrent' == $sickbeard.TORRENT_METHOD:
#set $torrent_method_text = {'utorrent': 'uTorrent'}
#set $use_default = 'to use the %s min <a href="%s/config/search/#core-component-group3">torrent search setting minumum default</a>' % ($sickbeard.TORRENT_SEED_TIME, $sbRoot) if $sickbeard.TORRENT_SEED_TIME else 'for the %s setting' % $torrent_method_text[$sickbeard.TORRENT_METHOD]
<div class="field-pair">
<label for="${curTorrentProvider.get_id()}_seed_time">
<span class="component-title" id="${curTorrentProvider.get_id()}_seed_time_desc">Seed time (provider default)</span>
<span class="component-desc">
<input type="number" name="${curTorrentProvider.get_id()}_seed_time" id="${curTorrentProvider.get_id()}_seed_time" value="$curTorrentProvider.seed_time" class="form-control input-sm input75" />
<p>set 1 or more minimum minutes for each item sent to $torrent_method_text[$sickbeard.TORRENT_METHOD]</p>
<div class="clear-left"><p>(leave blank $use_default)</p></div>
</span>
</label>
</div>
@ -489,6 +518,17 @@
</label>
</div>
#end if
#if $hasattr($curTorrentProvider, 'reject_m2ts'):
<div class="field-pair">
<label for="${curTorrentProvider.get_id()}_reject_m2ts">
<span class="component-title">Reject Blu-ray M2TS releases</span>
<span class="component-desc">
<input type="checkbox" name="${curTorrentProvider.get_id()}_reject_m2ts" id="${curTorrentProvider.get_id()}_reject_m2ts" <%= html_checked if curTorrentProvider.reject_m2ts else '' %>/>
<p>enable to ignore Blu-ray MPEG-2 Transport Stream container releases</p>
</span>
</label>
</div>
#end if
#if $hasattr($curTorrentProvider, 'enable_recentsearch') and $curTorrentProvider.supportsBacklog:
<div class="field-pair">
<label for="${curTorrentProvider.get_id()}_enable_recentsearch">
@ -707,4 +747,4 @@
//-->
</script>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -10,8 +10,8 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/configSearch.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/configSearch.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?v=$sbPID"></script>
#if $varExists('header')
<h1 class="header">$header</h1>
@ -113,18 +113,42 @@
<label>
<span class="component-title">Ignore result with any word</span>
<span class="component-desc">
<input type="text" name="ignore_words" value="$sickbeard.IGNORE_WORDS" class="form-control input-sm input350">
<input type="text" name="ignore_words" value="$sickbeard.IGNORE_WORDS" class="form-control input-sm input350"><p>(opt: start "regex:")</p>
<p class="clear-left note">ignore search result <em class="grey-text">if its title contains any</em> of these comma seperated words</p>
</span>
<span class="component-title">Shows with custom ignores</span>
<span class="component-desc">
#set $shows = []
#for $show in $using_rls_ignore_words
#set void = $shows.append('<a href="%s/home/editShow?show=%s" style="vertical-align:middle">%s</a>' % ($sbRoot, $show[0], $show[1]))
#end for
#if len($using_rls_ignore_words)
<p style="line-height:1.2em;margin-top:6px">#echo ', '.join($shows)#</p>
#else
<p style="line-height:1.2em;margin-top:7px">...will list here when in use</p>
#end if
</span>
</label>
</div>
<div class="field-pair">
<label>
<span class="component-title">Require at least one word</span>
<span class="component-title">Require all these words</span>
<span class="component-desc">
<input type="text" name="require_words" value="$sickbeard.REQUIRE_WORDS" class="form-control input-sm input350">
<p class="clear-left note">ignore search result <em class="grey-text">unless its title contains one</em> of these comma seperated words</p>
<input type="text" name="require_words" value="$sickbeard.REQUIRE_WORDS" class="form-control input-sm input350"><p>(opt: start "regex:")</p>
<p class="clear-left note">ignore search result <em class="grey-text">unless its title contains all</em> of these comma seperated words</p>
</span>
<span class="component-title">Shows with custom requires</span>
<span class="component-desc">
#set $shows = []
#for $show in $using_rls_require_words
#set void = $shows.append('<a href="%s/home/editShow?show=%s" style="vertical-align:middle">%s</a>' % ($sbRoot, $show[0], $show[1]))
#end for
#if len($using_rls_require_words)
<p style="line-height:1.2em;margin-top:6px">#echo ', '.join($shows)#</p>
#else
<p style="line-height:1.2em;margin-top:7px">...will list here when in use</p>
#end if
</span>
</label>
</div>
@ -473,9 +497,9 @@
<div class="field-pair" id="torrent_seed_time_option">
<label>
<span class="component-title">Minimum seeding time is</span>
<span class="component-title">Seed time (minimum default)</span>
<span class="component-desc"><input type="number" step="0.1" name="torrent_seed_time" id="torrent_seed_time" value="$sickbeard.TORRENT_SEED_TIME" class="form-control input-sm input100">
<p>hours. (default:'0' passes blank to client and '-1' passes nothing)</p></span>
<p>1 or more minutes. (0 or blank to use the client setting)</p></span>
</label>
</div>

View file

@ -10,7 +10,7 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/configSubtitles.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/configSubtitles.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.tokeninput.js"></script>

View file

@ -15,19 +15,19 @@
#import os.path, os
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/lib/jquery.bookmarkscroll.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.bookmarkscroll.js?v=$sbPID"></script>
<input type="hidden" id="sbRoot" value="$sbRoot">
<script type="text/javascript" src="$sbRoot/js/displayShow.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/sceneExceptionsTooltip.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/displayShow.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/sceneExceptionsTooltip.js?v=$sbPID"></script>
#if $sickbeard.USE_IMDB_INFO
<script type="text/javascript" src="$sbRoot/js/ratingTooltip.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/ratingTooltip.js?v=$sbPID"></script>
#end if
<script type="text/javascript" src="$sbRoot/js/ajaxEpSearch.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/ajaxEpSubtitles.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.collapser.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/ajaxEpSearch.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/ajaxEpSubtitles.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.collapser.min.js?v=$sbPID"></script>
<script type="text/javascript" charset="utf-8">
<!--
\$(document).ready(function(){
@ -116,7 +116,7 @@
#end if
<div id="details-wrapper">
<div id="details-right">
#if $seasonResults
#if 0 < len($seasonResults)
##There is a special/season_0?##
#set $season_special = (0, 1)[0 == int($seasonResults[-1]['season'])]
##
@ -397,7 +397,11 @@
#end if
##
#if 0 == len($sqlResults)
<div style="margin-top:50px"><h3>Episodes no longer exist for this show at the associated indexer</h3></div>
<div style="margin-top:50px">
<h3>Episodes do not exist for this show at the associated indexer
<a class="service" href="<%= anon_url(sickbeard.indexerApi(_show.indexer).config['show_url'], _show.indexerid) %>" onclick="window.open(this.href, '_blank'); return false;" title="Show $sickbeard.indexerApi($show.indexer).name info in new tab">$sickbeard.indexerApi($show.indexer).name</a>
</h3>
</div>
#else:
#for $epResult in $sqlResults
#set $epStr = '%sx%s' % ($epResult['season'], $epResult['episode'])
@ -518,7 +522,7 @@
<td class="col-name">
<img src="$sbRoot/images/info32.png" width="16" height="16" alt="" class="plotInfo#echo '%s" />' %\
('None', ('" id="plot_info_%s_%s_%s' % ($show.indexerid, $epResult['season'], $epResult['episode'])))[None is not $epResult['description'] and '' != $epResult['description']]#
$epResult['name']
<%= '<em class="tba grey-text">TBA</em>' if not epResult['name'] or 'TBA' == epResult['name'] else epResult['name'] %>
</td>
<td class="col-airdate">

View file

@ -13,44 +13,14 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script>
<script type="text/javascript" charset="utf-8">
<!--
\$(document).ready(function(){
\$.getJSON('$sbRoot/home/addShows/getIndexerLanguages', {}, function(data) {
var resultStr = '';
if (data.results.length == 0) {
flag = ' class="flag" style="background-image:url($sbRoot/images/flags/${show.lang}.png)"';
resultStr = '<option value="$show.lang" selected="selected" + flag>$show.lang</option>';
} else {
var current_lang_added = false;
\$.each(data.results, function(index, obj) {
if (obj == '$show.lang') {
selected = ' selected="selected"';
current_lang_added = true;
}
else {
selected = '';
}
flag = ' class="flag" style="background-image:url($sbRoot/images/flags/' + obj + '.png);"';
resultStr += '<option value="' + obj + '"' + selected + flag + '>' + obj + '</option>';
});
if (!current_lang_added)
resultStr += '<option value="$show.lang" selected="selected">$show.lang</option>';
}
\$('#indexerLangSelectEdit').html(resultStr)
});
});
//-->
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/editShow.js?v=$sbPID"></script>
<script>
var config = {
show_lang: "$show.lang",
show_isanime: #echo ['!1','!0'][$show.is_anime]#
}
</script>
#if $varExists('header')
<h1 class="header">$header</h1>
#else
@ -99,15 +69,26 @@
<span class="component-title">Scene exception</span>
<span class="component-desc">
<input type="text" id="SceneName" class="form-control form-control-inline input-sm input200">
<select id="SceneNameSeason" class="form-control form-control-inline input-sm input100" style="#echo ('visibility:hidden','float:left')[$show.anime]#">
<option value="-1">Series</option>
#if $show.anime:
#for $season in $seasonResults:
<option value="$season[0]">Season $season[0]</option>
#end for
#end if
</select>
<input class="btn btn-inline" type="button" value="Add" id="addSceneName">
<p class="clear-left note">add alternative release names found on search providers for <b class="boldest grey-text">$show.name</b></p>
#set $addSceneNameText = ('', ' or seasons')[$show.anime]
<p class="clear-left note">add alternative release names$addSceneNameText found on search providers for <b class="boldest grey-text">$show.name</b></p>
</span>
<span class="component-desc">
<div id="SceneException">
<h4 class="grey-text">Exceptions list (multi-selectable)</h4>
<select id="exceptions_list" name="exceptions_list" multiple="multiple" class="input200" style="min-height:90px; float:left" >
#for $cur_exception in $show.exceptions:
<option value="$cur_exception">$cur_exception</option>
<select id="exceptions_list" name="exceptions_list" multiple="multiple" class="input350" style="min-height:90px; float:left" >
#for $cur_exception_season in $show.exceptions:
#for $cur_exception in $show.exceptions[$cur_exception_season]:
<option value="$cur_exception_season|$cur_exception">S#echo ($cur_exception_season, '*')[$cur_exception_season == -1]#: $cur_exception</option>
#end for
#end for
</select>
<span><p class="note">this list overrides the original name<br />to search, it doesn't append to it</p></span>
@ -125,8 +106,8 @@
<span class="component-title">Ignore result with any word</span>
<span class="component-desc">
<input type="text" name="rls_ignore_words" id="rls_ignore_words" value="$show.rls_ignore_words" class="form-control form-control-inline input-sm input350">
<p>e.g. [word1,word2, ... ,word_n]</p>
<p class="note">ignore search result <em class="grey-text">if its title contains any</em> of these comma seperated words</p>
<p>e.g. [[regex:]word1, word2, ..., word_n, regex_n]</p>
<p class="note">ignore search result <em class="grey-text">if its title contains any</em> of these comma seperated words or regular expressions</p>
</span>
</label>
</div>
@ -136,8 +117,8 @@
<span class="component-title">Require at least one word</span>
<span class="component-desc">
<input type="text" name="rls_require_words" id="rls_require_words" value="$show.rls_require_words" class="form-control form-control-inline input-sm input350">
<p>e.g. [word1,word2, ... ,word_n]</p>
<p class="note">ignore search result <em class="grey-text">unless its title contains one</em> of these comma seperated words</p>
<p>e.g. [[regex:]word1, word2, ..., word_n, regex_n]</p>
<p class="note">ignore search result <em class="grey-text">unless its title contains one</em> of these comma seperated words or regular expressions</p>
</span>
</label>
</div>
@ -186,7 +167,7 @@
<span class="component-title">Scene numbering</span>
<span class="component-desc">
<input type="checkbox" name="scene" id="scene"#if $show.scene == 1 then $html_checked else ''#>
<p>search for episodes that are numbered by scene groups instead of by the TV network</p>
<p>search for episodes numbered by scene groups instead of by the TV network <em class="grey-text">(#if $show_has_scene_map then 'scene/manual numbers' else 'manual numbers only '# available)</em></p>
</span>
</label>
</div>
@ -248,73 +229,10 @@
#if $show.is_anime:
#import sickbeard.blackandwhitelist
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_blackwhitelist.tmpl')
<script type="text/javascript" src="$sbRoot/js/blackwhite.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/blackwhite.js?v=$sbPID"></script>
#end if
<input type="submit" id="submit" value="Submit" class="btn btn-primary" />
</form>
<script type="text/javascript" charset="utf-8">
<!--
var all_exceptions = new Array;
jQuery('#location').fileBrowser({ title: 'Select Show Location' });
\$('#submit').click(function(){
all_exceptions = []
\$('#exceptions_list option').each ( function() {
all_exceptions.push( \$(this).val() );
});
\$('#exceptions_list').val(all_exceptions);
#if $show.is_anime:
generate_bwlist()
#end if
});
\$('#addSceneName').click(function() {
var scene_ex = \$('#SceneName').val()
var option = \$('<option>')
all_exceptions = []
\$('#exceptions_list option').each ( function() {
all_exceptions.push( \$(this).val() )
});
\$('#SceneName').val('')
if (jQuery.inArray(scene_ex, all_exceptions) > -1 || (scene_ex == ''))
return
\$('#SceneException').show()
option.attr('value',scene_ex)
option.html(scene_ex)
return option.appendTo('#exceptions_list');
});
\$('#removeSceneName').click(function() {
\$('#exceptions_list option:selected').remove();
\$(this).toggle_SceneException()
});
$.fn.toggle_SceneException = function() {
all_exceptions = []
\$('#exceptions_list option').each ( function() {
all_exceptions.push( \$(this).val() );
});
if ('' == all_exceptions)
\$('#SceneException').hide();
else
\$('#SceneException').show();
}
\$(this).toggle_SceneException();
//-->
</script>
</div>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -20,7 +20,7 @@
#end if
#if 'daybyday' != $layout:
<script type="text/javascript" src="$sbRoot/js/ajaxEpSearch.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/ajaxEpSearch.js?v=$sbPID"></script>
#end if
#if $varExists('header')
@ -30,7 +30,7 @@
#end if
#if 'daybyday' == $layout:
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?v=$sbPID"></script>
<script type="text/javascript" charset="utf-8">
<!--
\$(document).ready(function(){
@ -201,7 +201,7 @@
#if 'list' == $layout:
<!-- start list view //-->
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?v=$sbPID"></script>
<script type="text/javascript" charset="utf-8">
<!--
\$.tablesorter.addParser({
@ -643,6 +643,7 @@
#end if
#else
#set $cur_result['state'] = $state_soon
#set $cur_result['state-title'] = ''
$shows_soon.append($cur_result)
#end if
#end for

View file

@ -8,158 +8,26 @@
#set global $sbPath = '..'
#set global $topmenu = 'home'
#set global $page_body_attr = 'show-list'
#set fuzzydate = 'airdate'
##
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" charset="utf-8">
<!--
\$.tablesorter.addParser({
id: 'loadingNames',
is: function(s) {
return false;
},
format: function(s) {
if (s.indexOf('Loading...') == 0)
return s.replace('Loading...', '000');
else
#if not $sickbeard.SORT_ARTICLE
return (s || '').replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1');
#else
return (s || '');
#end if
},
type: 'text'
});
\$.tablesorter.addParser({
id: 'quality',
is: function(s) {
return false;
},
format: function(s) {
return s.replace('hd1080p',5).replace('hd720p',4).replace('hd',3).replace('sd',2).replace('any',1).replace('custom',7);
},
type: 'numeric'
});
\$(document).ready(function(){
##
#for $curShowlist in $showlists
#set $curListID = $curShowlist[0]
#if 'poster' != $layout
\$('#$curListID:has(tbody tr)').tablesorter({
sortList: [[5,1],[1,0]],
textExtraction: {
0: function(node) { return \$(node).find('span').text().toLowerCase(); },
2: function(node) { return \$(node).find('span').text().toLowerCase(); },
3: function(node) { return \$(node).find('span').text().toLowerCase(); },
4: function(node) { return \$(node).find('span').attr('data-progress'); },
5: function(node) { return \$(node).find('i').attr('alt'); }
},
widgets: ['saveSort', 'zebra', 'stickyHeaders', 'filter'],
headers: {
0: { sorter: 'isoDate' },
1: { sorter: 'loadingNames' },
3: { sorter: 'quality' },
4: { sorter: 'eps' }
},
widgetOptions: {
filter_columnFilters: false,
filter_reset: '.resetshows'
},
sortStable: true
});
\$.tablesorter.filter.bindSearch( '#$curListID', \$('.search') );
#else
var \$container = [\$('#$curListID')]
jQuery.each(\$container, function (j) {
this.isotope({
itemSelector: '.show',
sortBy: '$sickbeard.POSTER_SORTBY',
sortAscending: $sickbeard.POSTER_SORTDIR,
layoutMode: 'masonry',
masonry: {
columnWidth: 12,
isFitWidth: true
},
getSortData: {
name: function( itemElem ) {
var name = \$( itemElem ).attr('data-name');
#if not $sickbeard.SORT_ARTICLE
return (name || '').replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1');
#else
return (name || '');
#end if
},
date: function( itemElem ) {
var date = \$( itemElem ).attr('data-date');
return date.length && parseInt( date, 10 ) || Number.POSITIVE_INFINITY;
},
network: '[data-network]',
progress: function( itemElem ) {
var progress = \$( itemElem ).children('.sort-data').attr('data-progress');
return progress.length && parseInt( progress, 10 ) || Number.NEGATIVE_INFINITY;
}
}
});
});
\$('#postersort').on( 'change', function() {
var sortValue = this.value;
\$('#$curListID').isotope({ sortBy: sortValue });
\$.get(this.options[this.selectedIndex].getAttribute('data-sort'));
});
\$('#postersortdirection').on( 'change', function() {
var sortDirection = this.value;
sortDirection = sortDirection == 'true';
\$('#$curListID').isotope({ sortAscending: sortDirection });
\$.get(this.options[this.selectedIndex].getAttribute('data-sort'));
});
#end if
#end for
#raw
$('img#network').on('error', function(){
$(this).parent().text($(this).attr('alt'));
$(this).remove();
});
#end raw
##
#set $fuzzydate = 'airdate'
#if $sickbeard.FUZZY_DATING
fuzzyMoment({
dtInline: #echo ('!1', '!0')['poster' == $layout]#,
containerClass: '.${fuzzydate}',
dateHasTime: !1,
dateFormat: '${sickbeard.DATE_PRESET}',
timeFormat: '${sickbeard.TIME_PRESET}',
trimZero: #echo ('!1', '!0')[$sickbeard.TRIM_ZERO]#
});
#end if
#if $sickbeard.HOME_SEARCH_FOCUS
\$('#search_show_name').focus();
#end if
});
//-->
</script>
##
#if $varExists('header')
<script>
var config = {
isPoster: #echo ['!1','!0']['poster' == $sickbeard.HOME_LAYOUT]#,
sortArticle: #echo ['!1','!0'][$sickbeard.SORT_ARTICLE]#,
homeSearchFocus: #echo ['!1','!0'][$sickbeard.HOME_SEARCH_FOCUS]#,
fuzzyDating: #echo ['!1','!0'][$sickbeard.FUZZY_DATING]#,
timeZero: #echo ['!1','!0'][$sickbeard.TRIM_ZERO]#,
datePreset: "$sickbeard.DATE_PRESET",
timePreset: "$sickbeard.TIME_PRESET",
posterSortby: "$sickbeard.POSTER_SORTBY",
posterSortdir: #echo ['!1','!0'][$sickbeard.POSTER_SORTDIR]#,
fuzzydate: ".$fuzzydate",
};
</script>
<script type="text/javascript" src="$sbRoot/js/home.js?v=$sbPID"></script>
<h1 class="header" style="margin-bottom:0">$showlists[0][1]</h1>
#else
<h1 class="title" style="margin-bottom:0">$title</h1>
#end if
#set $tab = 1
#if 'poster' != $layout
@ -324,7 +192,7 @@
<div class="show-date">
#if $cur_airs_next
#set $ldatetime = $sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($cur_airs_next,$curShow.airs,$curShow.network))
<span class="${fuzzydate}">$sbdatetime.sbdatetime.sbfdate($ldatetime)</span>
<span class="$fuzzydate">$sbdatetime.sbdatetime.sbfdate($ldatetime)</span>
#else
#set $output_html = '?'
#if None is not $display_status
@ -471,7 +339,7 @@
<tr>
#if $cur_airs_next
#set $ldatetime = $sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($cur_airs_next,$curShow.airs,$curShow.network))
<td class="text-nowrap"><div class="${fuzzydate}">$sbdatetime.sbdatetime.sbfdate($ldatetime)</div><span class="sort-data">$ldatetime.strftime('%Y%m%d%H%M')</span></td>
<td class="text-nowrap"><div class="$fuzzydate">$sbdatetime.sbdatetime.sbfdate($ldatetime)</div><span class="sort-data">$ldatetime.strftime('%Y%m%d%H%M')</span></td>
#else
<td></td>
#end if
@ -550,21 +418,6 @@
#end if
#end for
##
<script type="text/javascript">
<!--
#raw
$(document).ready(function(){
$('div[id^="progressbar"]').each(function(k, v){
var progress = parseInt($(this).siblings('span[class="sort-data"]').attr('data-progress'), 10) , elId = '#' + $(this).attr('id'), v = 80;
$(elId).progressbar({value:progress});
if (progress < 80) {
v = progress >= 40 ? 60 : (progress >= 20 ? 40 : 20);
}
$(elId + ' > .ui-progressbar-value').addClass('progress-' + v);
});
});
#end raw
//-->
</script>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -10,14 +10,11 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addExistingShow.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addShowOptions.js?$sbPID"></script>
<script type="text/javascript" charset="utf-8">
<!--
\$(document).ready(function(){
\$.sgSid = '$kwargs.get('sid', '')';
\$.sgHashDir = '$kwargs.get('hash_dir', '')';
\$(document).ready(function(){
\$( '#tabs' ).tabs({
collapsible: true,
selected: #if $sickbeard.ROOT_DIRS then '-1' else '0'#
@ -25,6 +22,10 @@
});
//-->
</script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addExistingShow.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addShowOptions.js?v=$sbPID"></script>
#if $varExists('header')
<h1 class="header">$header</h1>
@ -36,48 +37,56 @@
<form id="addShowForm" method="post" action="$sbRoot/home/addShows/addNewShow" accept-charset="utf-8">
<p>Tip: shows are added quicker when usable show nfo and xml metadata is found</p>
<span#if $kwargs.get('hash_dir', None)# style="display:none"#end if>
<p>Tip: shows are added quicker when usable show nfo and xml metadata is found</p>
<p style="margin-top:15px">
<input type="checkbox" id="promptForSettings" name="promptForSettings" style="vertical-align: top;" />
<label for="promptForSettings">Enable to change the following options per show, otherwise use these options with all shows added below</label>
</p>
<p style="margin-top:15px">
<input type="checkbox" id="promptForSettings" name="promptForSettings" style="vertical-align: top;" />
<label for="promptForSettings">Enable to change the following options per show, otherwise use these options with all shows added below</label>
</p>
<div id="tabs">
<ul>
<li><a href="#tabs-1">Manage parent folders</a></li>
<li><a href="#tabs-2">Custom options</a></li>
</ul>
<div id="tabs-1" class="existingtabs">
<div style="width: 430px; margin: 0px auto">
<div id="tabs">
<ul>
<li><a href="#tabs-1">Manage parent folders</a></li>
<li><a href="#tabs-2">Custom options</a></li>
</ul>
<div id="tabs-1" class="existingtabs">
<div style="width: 430px; margin: 0px auto">
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_rootDirs.tmpl')
</div>
</div>
</div>
<div id="tabs-2">
<div class="stepDiv">
<div id="tabs-2">
<div class="stepDiv">
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_addShowOptions.tmpl')
</div>
</div>
</div>
</div>
<br />
<hr />
<br />
<hr />
</span>
<p>The following parent folder(s) are scanned for existing shows. Toggle a folder to display shows</p>
<p>The following parent folder$multi_parents scanned for
#if not $kwargs.get('hash_dir', None)#existing shows. Toggle a folder to display shows#else#the existing show...#end if#
</p>
<ul id="rootDirStaticList">
<li></li>
</ul>
#if not $kwargs.get('hash_dir', None)
<p>shows <span class="boldest">not known</span> to SickGear are listed below...</p>
#end if
<div id="tableDiv"></div>
<br />
#if not $kwargs.get('hash_dir', None)
<p>If you tried to add a show, arrived here and can't see the folder, then that show may already be in your show list.</p>
#end if
<input class="btn btn-primary" type="button" value="Submit" id="submitShowDirs" />
<input class="btn btn-primary" type="button" value="#if $kwargs.get('hash_dir', None)#Redo Search#else#Submit#end if#" id="submitShowDirs" />
</form>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -10,55 +10,65 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
#if $varExists('header')
#if $varExists('header')
<h1 class="header">$header</h1>
#else
#else
<h1 class="title">$title</h1>
#end if
<div id="addShowPortal">
<a class="btn btn-large" href="$sbRoot/home/addShows/newShow/">
<div class="button"><div class="icon-addnewshow"></div></div>
<div class="buttontext">
<h3>Add New Show</h3>
<p>Search a TV database for a show to add. A new folder will be created for episodes</p>
<p>Search a TV database for a show to add.</p>
</div>
</a>
<a class="btn btn-large" href="$sbRoot/home/addShows/trendingShows/">
<div class="button"><div class="icon-addtrendingshow"></div></div>
<a class="btn btn-large" href="$sbRoot/home/addShows/trakt_default/">
<div class="button"><div class="icon-addrecommendedshow"></div></div>
<div class="buttontext">
<h3>Add From Trending</h3>
<p>Browse a current trending show list to add from. A folder for episodes will be created</p>
<h3>Add From Trakt</h3>
<p>Browse trends, recommended and more.</p>
</div>
</a>
<div style="clear:both;font-size:2px">&nbsp;</div>
<a class="btn btn-large" href="$sbRoot/home/addShows/existingShows/">
<div class="button"><div class="icon-addexistingshow"></div></div>
<div class="buttontext">
<h3>Add Existing Shows</h3>
<p>Scan parent folders for shows and episode metadata to import into SickGear</p>
<p>Scan parent folders to import into SickGear.</p>
</div>
</a>
#if True == $sickbeard.USE_TRAKT:
<a class="btn btn-large" href="$sbRoot/home/addShows/recommendedShows/">
<div class="button"><div class="icon-addrecommendedshow"></div></div>
<a class="btn btn-large" href="$sbRoot/home/addShows/popular_imdb/">
<div class="button"><div class="icon-addtrendingshow"></div></div>
<div class="buttontext">
<h3>Add Recommended</h3>
<p>Browse recommendations based on your Trakt.tv show library to add to SickGear</p>
<h3>Add From IMDb</h3>
<p>Browse popular for a show to add.</p>
</div>
</a>
<div style="clear:both;font-size:2px">&nbsp;</div>
#if $sickbeard.USE_ANIDB
<a class="btn btn-large" href="$sbRoot/home/addShows/randomhot_anidb/" style="float:right">
<div class="button"><div class="icon-addtrendingshow"></div></div>
<div class="buttontext">
<h3>Add from AniDB</h3>
<p>Browse what's hot and recommnended.</p>
</div>
</a>
#else
<div class="buttontext" style="padding:10px 5px">
<p>There's more... unlock another button to browse<br />
recommended shows based on your Trakt.tv show<br />
library by enabling Trakt in Config/Notifications/Social</p>
<div class="buttontext" style="padding:10px 5px 10px 30px">
<h3>Add Random/Hot AniDB</h3>
<p>To use, enable AniDB in Config/Anime.</p>
</div>
#end if
</div>
<div style="clear:both">&nbsp;</div>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -0,0 +1,265 @@
#import sickbeard
#import datetime
#import re
#import urllib
#from sickbeard.common import *
#from sickbeard import sbdatetime
#from sickbeard.helpers import anon_url
##
#set global $title='Browse Shows'
#set global $header='Browse Shows'
#set global $sbPath='..'
#set global $topmenu='home'
##
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?v=$sbPID"></script>
<script type="text/javascript" charset="utf-8">
<!--
#raw
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'}
});
});
#end raw
\$(document).ready(function(){
// initialise combos for dirty page refreshes
\$('#showsort').val('original');
\$('#showsortdirection').val('asc');
\$('#showfilter').val('*');
var \$container = [\$('#container')];
jQuery.each(\$container, function (j) {
this.isotope({
itemSelector: '.browse-show',
sortBy: 'original-order',
layoutMode: 'masonry',
masonry: {
columnWidth: 12,
isFitWidth: true
},
getSortData: {
premiered: '[data-premiered] parseInt',
name: function( itemElem ) {
var name = \$( itemElem ).attr('data-name') || '';
#if not $sickbeard.SORT_ARTICLE:
name = name.replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1');
#end if
return name.toLowerCase();
},
rating: '[data-rating] parseInt',
votes: '[data-votes] parseInt',
}
});
});
\$('#showsort').on( 'change', function() {
var sortCriteria;
switch (this.value) {
case 'original':
sortCriteria = 'original-order'
break;
case 'rating':
/* randomise, else the rating_votes can already
* have sorted leaving this with nothing to do.
*/
\$('#container').isotope({sortBy: 'random'});
sortCriteria = 'rating';
break;
case 'rating_votes':
sortCriteria = ['rating', 'votes'];
break;
case 'votes':
sortCriteria = 'votes';
break;
case 'premiered':
sortCriteria = 'premiered';
break;
default:
sortCriteria = 'name'
break;
}
\$('#container').isotope({sortBy: sortCriteria});
});
\$('#showsortdirection').on( 'change', function() {
\$('#container').isotope({sortAscending: ('asc' == this.value)});
});
\$('#showfilter').on( 'change', function() {
var filterValue = this.value;
if (-1 == filterValue.indexOf('trakt')) {
\$('#container').isotope({ filter: filterValue });
} else {
location = '$sbRoot/home/addShows/' + filterValue;
}
});
#raw
$('.service, .browse-image').each(addQTip);
#end raw
});
//-->
</script>
#if $varExists('header')
#set $heading = ('header', $header)
#else
#set $heading = ('title', $title)
#end if
<h1 style="margin-bottom:0" class="grey-text #echo '%s">%s' % $heading#</h1>
#if $all_shows or ($kwargs and $kwargs.get('show_header', None))
<div class="pull-right" style="margin-top:-35px">
<span>Show:</span>
<select id="showfilter" class="form-control form-control-inline input-sm">
#set $count_all_shows = len($all_shows)
#set $count_inlibrary = $all_shows_inlibrary
<option value="*" selected="selected">All<%= ' (%d)' % count_all_shows %></option>
<option value=".notinlibrary">Not In Library<%= ' (%d)' % (count_all_shows - count_inlibrary) %></option>
<option value=".inlibrary">In Library<%= ' (%d)' % count_inlibrary %></option>
#if 'Trakt' == $browse_type
#set $mode = $kwargs and $kwargs.get('mode', None)
#set $selected = ' class="selected"'
<optgroup label="Trakt">
<option value="trakt_anticipated"#echo ('', selected)['anticipated' == $mode]#>Anticipating</option>
<option value="trakt_newseasons"#echo ('', selected)['newseasons' == $mode]#>New Seasons</option>
<option value="trakt_newshows"#echo ('', selected)['newshows' == $mode]#>New Shows</option>
<option value="trakt_popular"#echo ('', selected)['popular' == $mode]#>Popular</option>
<option value="trakt_trending"#echo ('', selected)['trending' == $mode]#>Trending</option>
</optgroup>
<optgroup label="Trakt last month">
<option value="trakt_watched"#echo ('', selected)['watched' == $mode]#>Most Watched</option>
<option value="trakt_played"#echo ('', selected)['played' == $mode]#>Most Played</option>
<option value="trakt_collected"#echo ('', selected)['collected' == $mode]#>Most Collected</option>
</optgroup>
<optgroup label="Trakt last 12 months">
<option value="trakt_watched?period=year"#echo ('', selected)['watched-year' == $mode]#>Most Watched</option>
<option value="trakt_played?period=year"#echo ('', selected)['played-year' == $mode]#>Most Played</option>
<option value="trakt_collected?period=year"#echo ('', selected)['collected-year' == $mode]#>Most Collected</option>
</optgroup>
#if any($sickbeard.TRAKT_ACCOUNTS)
<optgroup label="Trakt recommended">
#for $account in $sickbeard.TRAKT_ACCOUNTS
#if $sickbeard.TRAKT_ACCOUNTS[$account].active and $sickbeard.TRAKT_ACCOUNTS[$account].name
<option value="trakt_recommended?account=$account"#echo ('', selected)['recommended-%s' % $account == $mode]#>for $sickbeard.TRAKT_ACCOUNTS[$account].name</option>
#end if
#end for
#else
<optgroup label="To get recommended">
<option value="trakt_recommended?action=add">Enable Trakt here</option>
#end if
</optgroup>
#end if
</select>
<span style="margin-left:12px">Sort By:</span>
<select id="showsort" class="form-control form-control-inline input-sm">
<option value="name">Name</option>
<option value="original" selected="selected">Original</option>
<option value="premiered">First aired</option>
<option value="votes">Votes</option>
<option value="rating">% Rating</option>
<option value="rating_votes">% Rating > Votes</option>
</select>
<span style="margin-left:12px">Sort Order:</span>
<select id="showsortdirection" class="form-control form-control-inline input-sm">
<option value="asc" selected="selected">Asc</option>
<option value="desc">Desc</option>
</select>
</div>
<h4 style="float:left;margin:0 0 0 2px">$browse_title</h4>
#if $kwargs and $kwargs.get('oldest', None):
<div class="grey-text" style="clear:both;margin-left:2px;font-size:0.85em">
First aired from $kwargs['oldest'] until $kwargs['newest']
</div>
#end if
#end if
<div id="container">
#if $all_shows
#for $this_show in $all_shows:
#set $title_html = $this_show['title'].replace('"', '&quot;').replace("'", '&#39;')
#if 'newseasons' == $kwargs.get('mode', '')
#set $overview = '%s: %s' % (
('Season %s' % $this_show['episode_season'], 'Brand-new')[1 == $this_show['episode_season']],
($this_show['overview'], $this_show['episode_overview'])[any($this_show['episode_overview']) and 1 != $this_show['episode_season']])
#else
#set $overview = $this_show['overview']
#end if
<div class="browse-show <%= ('notinlibrary', 'inlibrary')[':' in this_show['show_id']] %>" data-name="#echo re.sub(r'([\'\"])', r'', $this_show['title'])#" data-rating="$this_show['rating']" data-votes="$this_show['votes']" data-premiered="$this_show['premiered']">
<div class="browse-container">
<div class="browse-image">
<a class="browse-image" href="<%= anon_url(this_show['url_src_db']) %>" target="_blank"
title="<span style='color: rgb(66, 139, 202)'>$re.sub(r'(?m)\s+\((?:19|20)\d\d\)\s*$', '', $title_html)</span>#if $this_show['genres']#<br /><div style='font-weight:bold'>(<em>$this_show['genres']</em>)</div>#end if#
<p style='margin:0 0 2px'>#echo re.sub(r'([,\.!][^,\.!]*?)$', '...', re.sub(r'([!\?\.])(?=\w)', r'\1 ', $overview))#</p>
<p><span style='font-weight:bold;font-size:0.9em;color:#888'><em>#if $kwargs and 'newseasons' == $kwargs.get('mode', None)#Air#else#First air#end if##echo ('s', 'ed')[$this_show['when_past']]#: $this_show['premiered_str']</em></span></p>
<span style='float:right'>Click for more at <span class='boldest'>$browse_type</span></span>">
#if 'poster' in $this_show['images']:
#set $image = $this_show['images']['poster']['thumb']
<img alt="" class="browse-image" src="#if $image and 'http' != $image[:4]#$sbRoot/#end if#$image" />
#else:
<span>&nbsp;</span>
#end if
</a>
</div>
<div class="show-title">
<%= (this_show['title'], '<span>&nbsp;</span>')['' == this_show['title']] %>
</div>
<div class="clearfix">
<p>$this_show['rating']% <img src="$sbRoot/images/heart.png"><i>$this_show['votes'] votes</i></p>
#if 'url_tvdb' in $this_show and $this_show['url_tvdb']:
<a class="service" href="<%= anon_url(this_show['url_tvdb']) %>" onclick="window.open(this.href, '_blank'); return false;"
title="View <span class='boldest'>tvdb</span> detail for <span style='color: rgb(66, 139, 202)'>$title_html</span>">
<i><img style="margin-top:5px" alt="tvdb" height="16" width="16" src="$sbRoot/images/$sickbeard.indexerApi($sickbeard.indexers.indexer_config.INDEXER_TVDB).config['icon']" /></i></a>
#end if
<div class="browse-add-show-holder">
#if ':' in $this_show['show_id']:
<p style="line-height: 1.5; padding: 2px 5px 3px" title="<%= '%s added' % ('TVRage', 'theTVDB')['1' == this_show['show_id'][:1]] %>">In library</p>
#else
<a href="$sbRoot/home/addShows/add${browse_type}Show?indexer_id=${this_show['show_id']}&amp;showName=${urllib.quote($this_show['title'].encode("utf-8"))}" class="btn btn-xs">Add Show</a>
#end if
</div>
</div>
</div>
</div>
#end for
</div>
#if $kwargs and $kwargs.get('footnote', None):
<div>
$kwargs['footnote']
</div>
#end if
#else
<div class="browse-show" style="width:100%; margin-top:20px">
<p class="red-text">
#if $kwargs and $kwargs.get('error_msg', None):
$kwargs['error_msg']
#else
$browse_type API did not return results, this can happen from time to time.
<br /><br />This view should auto refresh every 10 mins.
#end if
</p>
</div>
</div>
#end if
<script type="text/javascript" charset="utf-8">
<!--
window.setInterval('location.reload(true)', 600000); // Refresh every 10 minutes
//-->
</script>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -2,48 +2,72 @@
#from sickbeard.helpers import anon_url
<table id="addRootDirTable" class="sickbeardTable tablesorter">
<thead><tr><th class="col-checkbox"><input type="checkbox" id="checkAll" checked=checked></th><th>Parent\show folder</th><th width="20%">Show name<br />(tvshow.nfo)<th width="15%">TV database</td></tr></thead>
<tfoot>
<tr>
<th rowspan="1" colspan="4" align="left"><a href="#" class="showManage">Manage Directories</a></th>
</tr>
</tfoot>
<tbody>
#for $curDir in $dirList:
#if $curDir['added_already']:
#continue
#end if
#set $show_id = $curDir['dir']
#if $curDir['existing_info'][0]:
#set $show_id = $show_id + '|' + $str($curDir['existing_info'][0]) + '|' + $str($curDir['existing_info'][1])
#set $indexer = $curDir['existing_info'][2]
#end if
#set $indexer = 0
#if $curDir['existing_info'][0]:
#set $indexer = $curDir['existing_info'][2]
#elif $sickbeard.INDEXER_DEFAULT > 0:
#set $indexer = $sickbeard.INDEXER_DEFAULT
#end if
<tr>
<td class="col-checkbox"><input type="checkbox" id="$show_id" class="dirCheck" checked=checked></td>
<td><label for="$show_id">$curDir['display_dir']</label></td>
#if $curDir['existing_info'][1] and $indexer > 0:
<td><a href="<%= anon_url(sickbeard.indexerApi(indexer).config['show_url'], curDir['existing_info'][0]) %>" target="_new">$curDir['existing_info'][1]</a></td>
#else:
<td>?</td>
<thead>
<tr>
<th class="col-checkbox">
<input type="checkbox" id="checkAll" checked=checked>
</th>
<th>Parent\show folder</th>
<th width="20%">Show name<br />(tvshow.nfo)</th>
<th width="15%">TV info source</th>
</tr>
</thead>
<tfoot>
<tr>
<th rowspan="1" colspan="4" align="left">
<a href="#" class="showManage">Manage Directories</a>
</th>
</tr>
</tfoot>
<tbody>
#for $curDir in $dirList
#if $curDir['added_already'] and (None is $curDir.get('highlight') or not $kwargs.get('hash_dir'))
#continue
#end if
<td align="center">
<select name="indexer">
#for $curIndexer in $sickbeard.indexerApi().indexers.items():
<option value="$curIndexer[0]" #if $curIndexer[0] == $indexer then "selected=\"selected\"" else "UNKNOWN"#>$curIndexer[1]</option>
#end for
</select>
</td>
</tr>
#set $show_id = $curDir['dir']
#if $curDir['existing_info'][0]
#set $show_id = $show_id + '|' + $str($curDir['existing_info'][0]) + '|' + $str($curDir['existing_info'][1])
#set $indexer = $curDir['existing_info'][2]
#end if
#set $indexer = $sickbeard.INDEXER_DEFAULT
#*
#set $indexer = 0
#if $curDir['existing_info'][0]
#set $indexer = $curDir['existing_info'][2]
#elif 0 < $sickbeard.INDEXER_DEFAULT
#set $indexer = $sickbeard.INDEXER_DEFAULT
#end if
*#
<tr>
<td class="col-checkbox">
<input type="checkbox" id="$show_id" class="dirCheck" checked=checked>
</td>
<td>
<label for="$show_id">
<span class="filepath#echo ('', ' red-text')[$curDir['highlight']]#">$curDir['path']</span>$curDir['name']
#echo ('', '<br />^ <span class="red-text">... (cannot add, this location is in use)</span>')[$curDir['highlight']]#
</label>
</td>
#if $curDir['existing_info'][1] and $indexer > 0
<td>
<a href="<%= anon_url(sickbeard.indexerApi(indexer).config['show_url'], curDir['existing_info'][0]) %>" target="_new">$curDir['existing_info'][1]</a>
</td>
#else
<td>?</td>
#end if
<td align="center">
<select name="indexer">
#for $curIndexer in $sickbeard.indexerApi().indexers.items()
#if $curIndexer[0] == $sickbeard.INDEXER_DEFAULT
<option value="$curIndexer[0]" #if $curIndexer[0] == $indexer then 'selected="selected"' else ''#>$curIndexer[1]</option>
#end if
#end for
</select>
</td>
</tr>
#end for
</tbody>
</tbody>
</table>
</tbody>
</table>

View file

@ -10,10 +10,14 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/formwizard.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/newShow.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addShowOptions.js?$sbPID"></script>
<script>
var show_scene_maps = ${show_scene_maps}
</script>
<script type="text/javascript" src="$sbRoot/js/formwizard.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/newShow.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addShowOptions.js?v=$sbPID"></script>
#if $varExists('header')
<h1 class="header">$header</h1>
@ -31,7 +35,7 @@
<form id="addShowForm" method="post" action="$sbRoot/home/addShows/addNewShow" accept-charset="utf-8">
<fieldset class="sectionwrap step-one">
<legend class="legendStep"><p>Find show at a TV database</p></legend>
<legend class="legendStep"><p>#if $use_provided_info#Using known show information#else#Find show at TV info source#end if#</p></legend>
<div class="stepDiv">
<input type="hidden" id="indexer_timeout" value="$sickbeard.INDEXER_TIMEOUT" />
@ -39,29 +43,38 @@
#if $use_provided_info
#set $provided_indexer_local = $provided_indexer
#set $provided_indexer_id_local = $provided_indexer_id
Show retrieved from existing metadata: <a href="<%= anon_url(sickbeard.indexerApi(provided_indexer_local).config['show_url'], provided_indexer_id_local) %>">$provided_indexer_name</a>
Show: <a href="<%= anon_url(sickbeard.indexerApi(provided_indexer_local).config['show_url'], provided_indexer_id_local) %>">$provided_indexer_name</a>
<input type="hidden" name="indexerLang" value="en" />
<input type="hidden" name="whichSeries" value="$provided_indexer_id" />
<input type="hidden" name="whichSeries" value="#echo '|'.join([str($provided_indexer), '', str($provided_indexer_id), $provided_indexer_name])#" />
<input type="hidden" id="providedName" value="$provided_indexer_name" />
<input type="hidden" id="providedIndexer" value="$provided_indexer" />
#else
#if 2 > $len($indexers)
<style>
#addShowForm input#nameToSearch{width:611px}
</style>
<input type="hidden" id="providedIndexer" value="$provided_indexer" />
#end if
<input type="text" id="nameToSearch" value="$default_show_name" class="form-control form-control-inline input-sm input350" />
&nbsp;
<span style="float:right">
<select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm">
<option value="en" selected="selected">en</option>
</select><b>&nbsp;*</b>
<select name="providedIndexer" id="providedIndexer" class="form-control form-control-inline input-sm">
<option value="0" #if $provided_indexer == 0 then "selected=\"selected\"" else ""#>All Indexers</option>
#for $indexer in $indexers
<option value="$indexer" #if $provided_indexer == $indexer then "selected=\"selected\"" else ""#>$indexers[$indexer]</option>
#end for
</select>
&nbsp;
<input class="btn btn-inline" type="button" id="searchName" value="Search" />
<select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm">
<option value="en" selected="selected">en</option>
</select><b>&nbsp;*</b>
#if 1 < $len($indexers)
<select name="providedIndexer" id="providedIndexer" class="form-control form-control-inline input-sm">
<option value="0" #if $provided_indexer == 0 then "selected=\"selected\"" else ""#>All Indexers</option>
#for $indexer in $indexers
<option value="$indexer" #if $provided_indexer == $indexer then "selected=\"selected\"" else ""#>$indexers[$indexer]</option>
#end for
</select>
#end if
&nbsp;
<input class="btn btn-inline" type="button" id="searchName" value="Search" />
</span>
<br />
<p style="margin:5px 0 15px"><b>*</b> SickGear supports english episodes. The language choice is used for fetching show data and episode filenames</p>
<p style="margin:5px 0 15px">Enter show name, TVDB ID, IMDb Url, or IMDb ID.&nbsp;&nbsp;<b>*</b>SickGear supports english, language is used for show/episode data</p>
<div id="searchResults" style="height: 100%"></div>
#end if
@ -107,9 +120,9 @@
#end if
</div>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/blackwhite.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/blackwhite.js?v=$sbPID"></script>
</div></div>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -11,10 +11,10 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
<script type="text/javascript" src="$sbRoot/js/formwizard.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/recommendedShows.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addShowOptions.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/formwizard.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/recommendedShows.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addShowOptions.js?v=$sbPID"></script>
#if $varExists('header')
<h1 class="header">$header</h1>
@ -65,7 +65,7 @@
<input class="btn" type="button" id="addShowButton" value="Add Show" disabled="disabled" />
</div>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?v=$sbPID"></script>
</div>

View file

@ -1,169 +0,0 @@
#import sickbeard
#import datetime
#import re
#import urllib
#from sickbeard.common import *
#from sickbeard import sbdatetime
#from sickbeard.helpers import anon_url
##
#set global $title='Trending Shows'
#set global $header='Trending Shows'
#set global $sbPath='..'
#set global $topmenu='episodeView'
##
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
<script type="text/javascript" charset="utf-8">
<!--
\$(document).ready(function(){
// initialise combos for dirty page refreshes
\$('#showsort').val('original');
\$('#showsortdirection').val('asc');
\$('#showfilter').val('*');
var \$container = [\$('#container')];
jQuery.each(\$container, function (j) {
this.isotope({
itemSelector: '.trakt_show',
sortBy: 'original-order',
layoutMode: 'fitRows',
getSortData: {
name: function( itemElem ) {
var name = \$( itemElem ).attr('data-name') || '';
#if not $sickbeard.SORT_ARTICLE:
name = name.replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1');
#end if
return name.toLowerCase();
},
rating: '[data-rating] parseInt',
votes: '[data-votes] parseInt',
}
});
});
\$('#showsort').on( 'change', function() {
var sortCriteria;
switch (this.value) {
case 'original':
sortCriteria = 'original-order'
break;
case 'rating':
/* randomise, else the rating_votes can already
* have sorted leaving this with nothing to do.
*/
\$('#container').isotope({sortBy: 'random'});
sortCriteria = 'rating';
break;
case 'rating_votes':
sortCriteria = ['rating', 'votes'];
break;
case 'votes':
sortCriteria = 'votes';
break;
default:
sortCriteria = 'name'
break;
}
\$('#container').isotope({sortBy: sortCriteria});
});
\$('#showsortdirection').on( 'change', function() {
\$('#container').isotope({sortAscending: ('asc' == this.value)});
});
\$('#showfilter').on( 'change', function() {
var filterValue = this.value;
\$('#container').isotope({ filter: filterValue });
});
});
//-->
</script>
#if $varExists('header')
<h1 class="header">$header</h1>
#else
<h1 class="title">$title</h1>
#end if
#if $trending_shows
<div class="pull-right" style="margin-top: -40px;">
<span>Show:</span>
<select id="showfilter" class="form-control form-control-inline input-sm">
#set $count_trending = len($trending_shows)
#set $count_inlibrary = $trending_inlibrary
<option value="*" selected="selected">All<%= ' (%d)' % count_trending %></option>
<option value=".notinlibrary">Not In Library<%= ' (%d)' % (count_trending - count_inlibrary) %></option>
<option value=".inlibrary">In Library<%= ' (%d)' % count_inlibrary %></option>
</select>
<span style="margin-left:12px">Sort By:</span>
<select id="showsort" class="form-control form-control-inline input-sm">
<option value="name">Name</option>
<option value="original" selected="selected">Original</option>
<option value="votes">Votes</option>
<option value="rating">% Rating</option>
<option value="rating_votes">% Rating > Votes</option>
</select>
<span style="margin-left:12px">Sort Order:</span>
<select id="showsortdirection" class="form-control form-control-inline input-sm">
<option value="asc" selected="selected">Asc</option>
<option value="desc">Desc</option>
</select>
</div>
#end if
<div id="container">
#if None is $trending_shows
<div class="trakt_show" style="width:100%; margin-top:20px">
<p class="red-text">Trakt API did not return results, this can happen from time to time.
<br /><br />This view should auto refresh every 10 mins.</p>
</div>
#else
#for $cur_show in $trending_shows:
#set $image = re.sub(r'(.*)/original/(.+)$', r'\1/thumb/\2', $cur_show['images']['poster'], 0, re.IGNORECASE | re.MULTILINE)
<div class="trakt_show <%= ('notinlibrary', 'inlibrary')[':' in cur_show['show_id']] %>" data-name="$cur_show['title']" data-rating="$cur_show['ratings']['percentage']" data-votes="$cur_show['ratings']['votes']">
<div class="traktContainer">
<div class="trakt-image">
<a class="trakt-image" href="<%= anon_url('https://trakt.tv', cur_show['url']) %>" target="_blank"><img alt="" class="trakt-image" src="${image}" /></a>
</div>
<div class="show-title">
<%= (cur_show['title'], '<span>&nbsp;</span>')[ '' == cur_show['title']] %>
</div>
<div class="clearfix">
<p>$cur_show['ratings']['percentage']% <img src="$sbRoot/images/heart.png"></p>
<i>$cur_show['ratings']['votes'] votes</i>
<div class="traktShowTitleIcons">
#if ':' in $cur_show['show_id']:
<p style="line-height: 1.5; padding: 2px 5px 3px" title="<%= '%s added' % ('TVRage', 'theTVDB')['1' == cur_show['show_id'][:1]] %>">In library</p>
#else
#set $encoded_show_title = urllib.quote($cur_show['title'].encode("utf-8"))
<a href="$sbRoot/home/addShows/addTraktShow?indexer_id=${cur_show['show_id']}&amp;showName=${encoded_show_title}" class="btn btn-xs">Add Show</a>
#end if
</div>
</div>
</div>
</div>
#end for
#end if
</div>
<script type="text/javascript" charset="utf-8">
<!--
window.setInterval('location.reload(true)', 600000); // Refresh every 10 minutes
//-->
</script>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -79,7 +79,7 @@
<span class="component-title">Scene numbering</span>
<span class="component-desc">
<input type="checkbox" name="scene" id="scene" #if $sickbeard.SCENE_DEFAULT then "checked=\"checked\"" else ""# />
<p>search for episodes that are numbered by scene groups instead of by the TV network</p>
<p>search for episodes numbered by scene groups instead of by the TV network<span id="scene-maps-found" style="display:none" class="grey-text"> (scene numbers found)</span></p>
</span>
</label>
</div>
@ -101,7 +101,7 @@
<label for="anime">
<span class="component-title">Show is anime</span>
<span class="component-desc">
<input type="checkbox" name="anime" id="anime" #if $sickbeard.ANIME_DEFAULT then "checked=\"checked\"" else ""# />
<input type="checkbox" name="anime" id="anime" #if $sickbeard.ANIME_DEFAULT or $kwargs.get('is_anime') then "checked=\"checked\"" else ""# />
<p>enable if this show is anime and episode releases are named ... <em class="grey-text">Show.265</em> instead of <em class="grey-text">Show.S02E03</em></p>
</span>
</label>

View file

@ -7,7 +7,7 @@
<span class="component-title input">Quality</span>
<span class="component-desc">
#set $selected = None
<select id="qualityPreset" class="form-control form-control-inline input-sm">
<select id="qualityPreset" name="quality_preset" class="form-control form-control-inline input-sm">
<option value="0">Custom</option>
#for $curPreset in sorted($qualityPresets):
<option value="$curPreset" #if $curPreset == $overall_quality then "selected=\"selected\"" else ""# #if $qualityPresetStrings[$curPreset].endswith("0p") then "style=\"padding-left: 15px;\"" else ""#>$qualityPresetStrings[$curPreset]</option>

View file

@ -5,11 +5,12 @@
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="robots" content="noindex, nofollow, noarchive, nocache, noodp, noydir, noimageindex, nosnippet">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>SickGear - $title</title>
<title>SickGear - BRANCH:[$sickbeard.BRANCH] - $title</title>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
@ -24,7 +25,7 @@
<link rel="apple-touch-icon" sizes="76x76" href="$sbRoot/images/ico/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="72x72" href="$sbRoot/images/ico/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="60x60" href="$sbRoot/images/ico/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="57x57" href="$sbRoot/images/ico/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="57x57" href="$sbRoot/images/ico/apple-touch-icon-57x57.png">
<link rel="icon" type="image/png" href="$sbRoot/images/ico/favicon-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="$sbRoot/images/ico/favicon-160x160.png" sizes="160x160">
<link rel="icon" type="image/png" href="$sbRoot/images/ico/favicon-96x96.png" sizes="96x96">
@ -34,92 +35,47 @@
<meta name="msapplication-TileImage" content="$sbRoot/images/ico/mstile-144x144.png">
<meta name="msapplication-config" content="$sbRoot/css/browserconfig.xml">
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/bootstrap.css?$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/browser.css?$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/jquery-ui-1.10.4.custom.css?$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/jquery.qtip-2.2.1.min.css?$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/pnotify.custom.min.css?$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/style.css?$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/${sickbeard.THEME_NAME}.css?$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/bootstrap.css?v=$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/browser.css?v=$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/jquery-ui-1.10.4.custom.css?v=$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/jquery.qtip-2.2.1.min.css?v=$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/pnotify.custom.min.css?v=$sbPID" />
<link rel="stylesheet" type="text/css" href="$sbRoot/css/style.css?v=$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/${sickbeard.THEME_NAME}.css?v=$sbPID" />
<script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/bootstrap.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/bootstrap-hover-dropdown.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery-ui-1.10.4.custom.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.cookie.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.cookiejar.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.json-2.2.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.selectboxes.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.tablesorter-2.17.7.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.tablesorter.widgets-2.17.7.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.qtip-2.2.1.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/pnotify.custom.min.js"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.form-3.35.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.ui.touch-punch-0.2.2.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/isotope.pkgd.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/imagesloaded.pkgd.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.confirm.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/script.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/bootstrap.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/bootstrap-hover-dropdown.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery-ui-1.10.4.custom.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.cookie.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.cookiejar.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.json-2.2.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.selectboxes.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.tablesorter-2.17.7.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.tablesorter.widgets-2.17.7.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.qtip-2.2.1.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/pnotify.custom.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.form-3.35.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.ui.touch-punch-0.2.2.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/isotope.pkgd.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/imagesloaded.pkgd.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.confirm.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/script.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/inc_top.js?v=$sbPID"></script>
#if $sickbeard.FUZZY_DATING
<script type="text/javascript" src="$sbRoot/js/moment/moment.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/fuzzyMoment.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/moment/moment.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/fuzzyMoment.js?v=$sbPID"></script>
#end if
<script type="text/javascript" charset="utf-8">
<!--
var sbRoot = '$sbRoot', anonURL = '$sickbeard.ANON_REDIRECT', themeSpinner = '#echo ('', '-dark')['dark' == $sickbeard.THEME_NAME]#',
top_image_html = '<img src="$sbRoot/images/top.gif" width="31" height="11" alt="Jump to top" />';
top_image_html = '<img src="$sbRoot/images/top.gif" width="31" height="11" alt="Jump to top" />', topmenu = '$topmenu';
//-->
</script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.scrolltopcontrol-1.1.js"></script>
<script type="text/javascript" src="$sbRoot/js/browser.js"></script>
<script type="text/javascript" src="$sbRoot/js/ajaxNotifications.js"></script>
<script type="text/javascript">
<!--
#raw
function initActions(){
$('#SubMenu a[href*="/home/restart/"]').addClass('btn restart').html('<i class="sgicon-restart"></i>Restart');
$('#SubMenu a[href*="/home/shutdown/"]').addClass('btn shutdown').html('<i class="sgicon-shutdown"></i>Shutdown');
$('#SubMenu a[href*="/home/logout/"]').addClass('btn').html('<i class="sgicon-logout"></i>Logout');
$('#SubMenu a:contains("Edit")').addClass('btn').html('<i class="sgicon-edit"></i>Edit');
$('#SubMenu a:contains("Remove")').addClass('btn remove').html('<i class="sgicon-delete"></i>Remove');
$('#SubMenu a:contains("Clear History")').addClass('btn clearhistory').html('<i class="sgicon-delete"></i>Clear History');
$('#SubMenu a:contains("Trim History")').addClass('btn trimhistory').html('<i class="sgicon-trim"></i>Trim History');
$('#SubMenu a[href$="/errorlogs/clearerrors/"]').addClass('btn').html('<i class="sgicon-delete"></i>Clear Errors');
$('#SubMenu a:contains("Re-scan")').addClass('btn').html('<i class="sgicon-refresh"></i>Re-scan');
$('#SubMenu a:contains("Backlog Overview")').addClass('btn').html('<i class="sgicon-backlog"></i>Backlog Overview');
$('#SubMenu a[href$="/home/updatePLEX/"]').addClass('btn').html('<i class="sgicon-plex"></i>Update PLEX');
$('#SubMenu a:contains("Force")').addClass('btn').html('<i class="sgicon-fullupdate"></i>Force Full Update');
$('#SubMenu a:contains("Rename")').addClass('btn').html('<i class="sgicon-rename"></i>Preview Rename');
$('#SubMenu a[href$="/config/subtitles/"]').addClass('btn').html('<i class="sgicon-subtitles"></i>Search Subtitles');
$('#SubMenu a[href*="/home/subtitleShow"]').addClass('btn').html('<i class="sgicon-subtitles"></i>Download Subtitles');
$('#SubMenu a:contains("Anime")').addClass('btn').html('<i class="sgicon-anime"></i>Anime');
$('#SubMenu a:contains("Settings")').addClass('btn').html('<i class="sgicon-search"></i>Search Settings');
$('#SubMenu a:contains("Provider")').addClass('btn').html('<i class="sgicon-search"></i>Search Providers');
$('#SubMenu a:contains("General")').addClass('btn').html('<i class="sgicon-config"></i>General');
$('#SubMenu a:contains("Episode Status")').addClass('btn').html('<i class="sgicon-episodestatus"></i>Episode Status Management');
$('#SubMenu a:contains("Missed Subtitle")').addClass('btn').html('<i class="sgicon-subtitles"></i>Missed Subtitles');
$('#SubMenu a[href$="/home/addShows/"]').addClass('btn').html('<i class="sgicon-addshow"></i>Add Show');
$('#SubMenu a:contains("Processing")').addClass('btn').html('<i class="sgicon-postprocess"></i>Post-Processing');
$('#SubMenu a:contains("Manage Searches")').addClass('btn').html('<i class="sgicon-search"></i>Manage Searches');
$('#SubMenu a:contains("Manage Torrents")').addClass('btn').html('<i class="sgicon-bittorrent"></i>Manage Torrents');
$('#SubMenu a:contains("Show Queue Overview")').addClass('btn').html('<i class="sgicon-showqueue"></i>Show Queue Overview');
$('#SubMenu a[href$="/manage/failedDownloads/"]').addClass('btn').html('<i class="sgicon-failed"></i>Failed Downloads');
$('#SubMenu a:contains("Notification")').addClass('btn').html('<i class="sgicon-notification"></i>Notifications');
$('#SubMenu a:contains("Update show in XBMC")').addClass('btn').html('<i class="sgicon-xbmc"></i>Update show in XBMC');
$('#SubMenu a[href$="/home/updateXBMC/"]').addClass('btn').html('<i class="sgicon-xbmc"></i>Update XBMC');
$('#SubMenu a:contains("Update show in Kodi")').addClass('btn').html('<i class="sgicon-kodi"></i>Update show in Kodi');
$('#SubMenu a[href$="/home/updateKODI/"]').addClass('btn').html('<i class="sgicon-kodi"></i>Update Kodi');
}
#end raw
\$(document).ready(function(){
initActions();
\$('#NAV$topmenu').addClass('active');
\$('.dropdown-toggle').dropdownHover();
});
//-->
</script>
<script type="text/javascript" src="$sbRoot/js/confirmations.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/confirmations.js?v=$sbPID"></script>
</head>
#set $tab = 4
#set $body_attr = ''
@ -178,11 +134,6 @@
#if $sickbeard.USE_KODI and $sickbeard.KODI_HOST != ''
<li><a href="$sbRoot/home/updateKODI/" tabindex="$tab#set $tab += 1#"><i class="sgicon-kodi"></i>Update Kodi</a></li>
#end if
#if $sickbeard.USE_TORRENTS and $sickbeard.TORRENT_METHOD != 'blackhole' \
and ($sickbeard.ENABLE_HTTPS and $sickbeard.TORRENT_HOST[:5] == 'https' \
or not $sickbeard.ENABLE_HTTPS and $sickbeard.TORRENT_HOST[:5] == 'http:')
<li><a href="$sbRoot/manage/manageTorrents/" tabindex="$tab#set $tab += 1#"><i class="sgicon-bittorrent"></i>Manage Torrents</a></li>
#end if
#if $sickbeard.USE_FAILED_DOWNLOADS
<li><a href="$sbRoot/manage/failedDownloads/" tabindex="$tab#set $tab += 1#"><i class="sgicon-failed"></i>Failed Downloads</a></li>
#end if
@ -217,9 +168,11 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" data-delay="0" tabindex="$tab#set $tab += 1#"><img src="$sbRoot/images/menu/system18-2.png" class="navbaricon hidden-xs" /><b class="caret hidden-xs"></b><span class="visible-xs">System <b class="caret"></b></span></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/manage/manageSearches/forceVersionCheck" tabindex="$tab#set $tab += 1#"><i class="sgicon-updatecheck"></i>Force Version Check</a></li>
<li><a href="$sbRoot/manage/manageSearches/forceVersionCheck" tabindex="$tab#set $tab += 1#"><i class="sgicon-updatecheck"></i>Check for Updates</a></li>
<li><a href="$sbRoot/home/viewchanges" tabindex="$tab#set $tab += 1#"><i class="sgicon-log"></i>View Changes</a></li>
<li class="divider"></li>
#if $sickbeard.WEB_USERNAME or $sickbeard.WEB_PASSWORD
<li><a href="$sbRoot/logout" class="confirm logout" tabindex="$tab#set $tab += 1#"><i class="sgicon-logout"></i>Logout</a></li>
<li><a href="$sbRoot/logout" class="confirm logout" tabindex="$tab#set $tab += 1#"><i class="sgicon-logout"></i>Logout</a></li>
#end if
<li><a href="$sbRoot/home/restart/?pid=$sbPID" class="confirm restart" tabindex="$tab#set $tab += 1#"><i class="sgicon-restart"></i>Restart</a></li>
<li><a href="$sbRoot/home/shutdown/?pid=$sbPID" class="confirm shutdown" tabindex="$tab#set $tab += 1#"><i class="sgicon-shutdown"></i>Shutdown</a></li>
@ -259,6 +212,14 @@
#end if
##
#if sys.version_info < (2, 7, 9):
<div class="alert alert-danger upgrade-notification" role="alert">
<span>
SickGear will be dropping support for Python 2.7.8 and below. We recommend updating to latest version:
<a href="https://www.python.org/downloads/" onclick="window.open(this.href); return false;">Download here</a>
</span>
</div>
#end if
#if $sickbeard.NEWEST_VERSION_STRING
<div class="alert alert-success upgrade-notification" role="alert">
<span>$sickbeard.NEWEST_VERSION_STRING</span>

View file

@ -84,7 +84,7 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
});
//-->
</script>
<script type="text/javascript" src="$sbRoot/js/massUpdate.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/massUpdate.js?v=$sbPID"></script>
#if $varExists('header')
<h1 class="header">$header</h1>
#else

View file

@ -10,9 +10,9 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
#if $varExists('header')
#if $varExists('header')
<h1 class="header">$header</h1>
#else
#else
<h1 class="title">$title</h1>
#end if
##
@ -28,7 +28,7 @@
<select name="whichStatus" class="form-control form-control-inline input-sm" style="margin:0 10px">
#for $curStatus in [$common.SKIPPED, $common.UNKNOWN, $common.SNATCHED, $common.WANTED, $common.ARCHIVED, $common.IGNORED]:
<option value="$curStatus">$common.statusStrings[$curStatus]</option>
<option value="$curStatus"#echo ('', ' selected="selected"')[$curStatus == $default_manage]#>$common.statusStrings[$curStatus]</option>
#end for
</select>
@ -36,8 +36,10 @@
</form>
##
#else
#if $whichStatus in ($common.ARCHIVED, $common.IGNORED, $common.SNATCHED):
#if $whichStatus in ($common.ARCHIVED, $common.IGNORED):
#set $row_class = 'good'
#elif $whichStatus == $common.SNATCHED:
#set $row_class = 'snatched'
#else
#set $row_class = $common.Overview.overviewStrings[$whichStatus]
#end if
@ -51,7 +53,7 @@
$statusList.append($common.FAILED)
#end if
<script type="text/javascript" src="$sbRoot/js/manageEpisodeStatuses.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/manageEpisodeStatuses.js?v=$sbPID"></script>
<form action="$sbRoot/manage/changeEpisodeStatuses" method="post">
<input type="hidden" id="oldStatus" name="oldStatus" value="$whichStatus">
@ -108,4 +110,4 @@
</form>
#end if
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -30,7 +30,7 @@
});
//-->
</script>
<script type="text/javascript" src="$sbRoot/js/failedDownloads.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/failedDownloads.js?v=$sbPID"></script>
#if $varExists('header')
<h1 class="header">$header</h1>

View file

@ -8,8 +8,8 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/manageSearches.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/manageSearches.js?v=$sbPID"></script>
<div id="content800">
#if $varExists('header')
<h1 class="header">$header</h1>

View file

@ -16,8 +16,8 @@
#set $initial_quality = $common.SD
#end if
#set $anyQualities, $bestQualities = $common.Quality.splitQuality($initial_quality)
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/massEdit.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/massEdit.js?v=$sbPID"></script>
<form action="massEditSubmit" method="post">
<input type="hidden" name="toEdit" value="$showList">
@ -60,11 +60,11 @@
</select>
</div><br />
<div id="customQuality">
<div id="customQuality" class="show-if-quality-custom">
<div class="manageCustom pull-left">
<h4 style="font-size:14px">Initial</h4>
#set $anyQualityList = filter(lambda x: x > $common.Quality.NONE, $common.Quality.qualityStrings)
<select id="anyQualities" name="anyQualities" multiple="multiple" size="len($anyQualityList)">
<select id="anyQualities" name="anyQualities" multiple="multiple" size="$len($anyQualityList)">
#for $curQuality in sorted($anyQualityList):
<option value="$curQuality" #if $curQuality in $anyQualities then $selected else ''#>$common.Quality.qualityStrings[$curQuality]</option>
#end for
@ -73,7 +73,7 @@
<div class="manageCustom pull-left">
<h4 style="font-size:14px">Upgrade to</h4>
#set $bestQualityList = filter(lambda x: x > $common.Quality.SDTV, $common.Quality.qualityStrings)
<select id="bestQualities" name="bestQualities" multiple="multiple" size="len($bestQualityList)">
<select id="bestQualities" name="bestQualities" multiple="multiple" size="$len($bestQualityList)">
#for $curQuality in sorted($bestQualityList):
<option value="$curQuality" #if $curQuality in $bestQualities then $selected else ''#>$common.Quality.qualityStrings[$curQuality]</option>
#end for

View file

@ -9,7 +9,7 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/manageShowQueueOverview.js?$sbPID" xmlns="http://www.w3.org/1999/html"></script>
<script type="text/javascript" src="$sbRoot/js/manageShowQueueOverview.js?v=$sbPID" xmlns="http://www.w3.org/1999/html"></script>
<div id="content800">
#if $varExists('header')
<h1 class="header">$header</h1>

View file

@ -3,8 +3,8 @@
#from lib import subliminal
#from sickbeard import common
##
#set global $title = 'Episode Overview'
#set global $header = 'Episode Overview'
#set global $title = 'Missing Subtitles'
#set global $header = 'Missing Subtitles'
#set global $sbPath = '..'
#set global $topmenu = 'manage'
##
@ -12,9 +12,9 @@
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<div id="content960">
#if $varExists('header')
#if $varExists('header')
<h1 class="header">$header</h1>
#else
#else
<h1 class="title">$title</h1>
#end if
##
@ -41,7 +41,7 @@
</form>
#else
<script type="text/javascript" src="$sbRoot/js/manageSubtitleMissed.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/manageSubtitleMissed.js?v=$sbPID"></script>
<input type="hidden" id="selectSubLang" name="selectSubLang" value="$whichSubs">
<form action="$sbRoot/manage/downloadSubtitleMissed" method="post">
@ -64,4 +64,4 @@
</form>
#end if
</div>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -1,23 +0,0 @@
#import sickbeard
#import datetime
#from sickbeard.common import *
##
#set global $title = 'Manage Torrents'
#set global $header = 'Manage Torrents'
#set global $sbPath = '..'
#set global $topmenu = 'manage'
##
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
#if $varExists('header')
<h1 class="header">$header</h1>
#else
<h1 class="title">$title</h1>
#end if
$info_download_station
<iframe id="extFrame" src="$webui_url" width="100%" height="500" frameBorder="0" style="border: 1px black solid;"></iframe>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -21,8 +21,8 @@ sbHost = "$curSBHost";
//-->
</script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/restart.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/restart.js?v=$sbPID"></script>
#set themeSpinner = '-dark' if 'dark' == themeSpinner else ''
<h2>Performing Restart</h2>

View file

@ -0,0 +1,20 @@
#import sickbeard
##
#set global $header = 'Changes'
#set global $title = 'Changes'
#set global $topmenu = ''
##
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<h1 class="header">$header</h1>
<div class="align-left" style="margin:30px 0">
<div id="changes">
#for $i, $change in $enumerate($changelist)##if 'rel' == $change['type']#
<div class="release#echo ('', ' old')[bool($i)]#"><span class="ver">$change['ver']</span> <span class="change-date grey-text">$change['date']</span></div>
#else# <div><span class="btn-text change-$change['type'].lower()">$change['type']</span> <span class="change-text">$change['text']</span></div>
#end if##end for#
</div>
</div>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -28,6 +28,7 @@ $(document).ready(function(){
window.location.href = sbRoot + '/home/addShows/addExistingShows'
+ '?promptForSettings=' + ($('#promptForSettings').prop('checked') ? 'on' : 'off')
+ (undefined !== $.sgSid && 0 < $.sgSid.length ? '&sid=' + $.sgSid : '')
+ '&shows_to_add=' + dirArr.join('&shows_to_add=');
});
@ -47,7 +48,7 @@ $(document).ready(function(){
+ ' height="32" width="32" />'
+ ' scanning parent folders...');
$.get(sbRoot + '/home/addShows/massAddTable',
$.get(sbRoot + '/home/addShows/massAddTable' + (undefined !== $.sgHashDir && 0 < $.sgHashDir.length ? '?hash_dir=' + $.sgHashDir : ''),
url,
function(data){
$('#tableDiv').html(data);
@ -90,4 +91,4 @@ $(document).ready(function(){
$('html,body').animate({scrollTop: 0}, 1000);
});
});
});

View file

@ -19,7 +19,8 @@ $(document).ready(function(){
default_flatten_folders: $('#flatten_folders').prop('checked'),
default_scene: $('#scene').prop('checked'),
default_subtitles: $('#subtitles').prop('checked'),
default_anime: $('#anime').prop('checked')
default_anime: $('#anime').prop('checked'),
default_tag: $('#tag').val()
});
new PNotify({
@ -32,7 +33,7 @@ $(document).ready(function(){
});
$('#statusSelect, #qualityPreset, #anyQualities, #bestQualities, #wanted_begin, #wanted_latest,'
+ ' #flatten_folders, #scene, #subtitles, #anime').change(function() {
+ ' #flatten_folders, #scene, #subtitles, #anime, #tag').change(function() {
$('#saveDefaultsButton').attr('disabled', false);
});

View file

@ -1,181 +1,208 @@
;(function($) {
"use strict";
'use strict';
$.Browser = {
defaults: {
title: 'Choose Directory',
url: sbRoot + '/browser/',
autocompleteURL: sbRoot + '/browser/complete',
includeFiles: 0
}
};
$.Browser = {
defaults: {
title: 'Choose Directory (or enter manually)',
url: sbRoot + '/browser/',
autocompleteURL: sbRoot + '/browser/complete',
includeFiles: 0,
showBrowseButton: !0
}
};
var fileBrowserDialog, currentBrowserPath, currentRequest = null;
var fileBrowserDialog, currentBrowserPath, currentRequest = null;
function browse(path, endpoint, includeFiles) {
function browse(path, endpoint, includeFiles) {
if (currentBrowserPath == path) {
return;
}
if (path === currentBrowserPath) {
return;
}
currentBrowserPath = path;
currentBrowserPath = path;
if (currentRequest) {
currentRequest.abort();
}
if (currentRequest) {
currentRequest.abort();
}
fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog busy');
fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog busy');
currentRequest = $.getJSON(endpoint, { path: path, includeFiles: includeFiles }, function (data) {
fileBrowserDialog.empty();
var first_val = data[0];
var i = 0;
var list, link = null;
data = $.grep(data, function (value) {
return i++ != 0;
});
$('<h2>').text(first_val.current_path).appendTo(fileBrowserDialog);
list = $('<ul>').appendTo(fileBrowserDialog);
$.each(data, function (i, entry) {
link = $("<a href='javascript:void(0)' />").click(function () { browse(entry.path, endpoint, includeFiles); }).text(entry.name);
$('<span class="ui-icon ui-icon-folder-collapsed"></span>').prependTo(link);
link.hover(
function () {$("span", this).addClass("ui-icon-folder-open"); },
function () {$("span", this).removeClass("ui-icon-folder-open"); }
);
link.appendTo(list);
});
$("a", list).wrap('<li class="ui-state-default ui-corner-all">');
fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog');
});
}
currentRequest = $.getJSON(endpoint, {path: path, includeFiles: includeFiles}, function(data){
fileBrowserDialog.empty();
var firstVal = data[0], i = 0, list, link = null;
data = $.grep(data, function(){
return i++ != 0;
});
$('<input type="text" class="form-control input-sm">')
.val(firstVal.currentPath)
.on('keypress', function(e){
if (13 === e.which) {
browse(e.target.value, endpoint, includeFiles);
}
})
.appendTo(fileBrowserDialog)
.fileBrowser({showBrowseButton: !1})
.on('autocompleteselect',
function(e, ui){browse(ui.item.value, endpoint, includeFiles);
});
$.fn.nFileBrowser = function (callback, options) {
options = $.extend({}, $.Browser.defaults, options);
list = $('<ul>').appendTo(fileBrowserDialog);
$.each(data, function(i, entry){
link = $('<a href="javascript:void(0)">').on('click',
function(){
if (entry.isFile) {
currentBrowserPath = entry.path;
$('.browserDialog .ui-button:contains("Ok")').click();
} else {
browse(entry.path, endpoint, includeFiles);
}
}).text(entry.name);
// make a fileBrowserDialog object if one doesn't exist already
if (!fileBrowserDialog) {
if (entry.isFile) {
link.prepend('<span class="ui-icon ui-icon-blank"></span>');
} else {
link.prepend('<span class="ui-icon ui-icon-folder-collapsed"></span>')
.on('mouseenter', function(){$('span', this).addClass('ui-icon-folder-open');})
.on('mouseleave', function(){$('span', this).removeClass('ui-icon-folder-open');});
}
link.appendTo(list);
});
$('a', list).wrap('<li class="ui-state-default ui-corner-all">');
fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog');
});
}
// set up the jquery dialog
fileBrowserDialog = $('<div id="fileBrowserDialog" style="display:hidden"></div>').appendTo('body').dialog({
dialogClass: 'browserDialog',
title: options.title,
position: ['center', 40],
minWidth: Math.min($(document).width() - 80, 650),
height: Math.min($(document).height() - 80, $(window).height() - 80),
maxHeight: Math.min($(document).height() - 80, $(window).height() - 80),
maxWidth: $(document).width() - 80,
modal: true,
autoOpen: false
});
}
$.fn.nFileBrowser = function(callback, options){
options = $.extend({}, $.Browser.defaults, options);
fileBrowserDialog.dialog('option', 'buttons', [
{
text: "Ok",
"class": "btn",
click: function() {
// store the browsed path to the associated text field
callback(currentBrowserPath, options);
$(this).dialog("close");
}
},
{
text: "Cancel",
"class": "btn",
click: function() {
$(this).dialog("close");
}
}
]);
// make a fileBrowserDialog object if one doesn't exist already
if (fileBrowserDialog) {
fileBrowserDialog.dialog('option', 'title', options.title);
} else {
// set up the jquery dialog
var docWidth = $(document).width(), dlgWidth = Math.min(docWidth - 80, 650),
docHeight = $(document).height() - 80, winHeight = $(window).height() - 80;
fileBrowserDialog = $('<div id="fileBrowserDialog" style="display:none"></div>').appendTo('body').dialog({
dialogClass: 'browserDialog',
title: options.title,
position: [(docWidth - dlgWidth)/2, 60],
minWidth: dlgWidth,
height: Math.min(docHeight, winHeight),
maxHeight: Math.min(docHeight, winHeight),
maxWidth: docWidth - 80,
modal: true,
autoOpen: false
});
}
// set up the browser and launch the dialog
var initialDir = '';
if (options.initialDir) {
initialDir = options.initialDir;
}
fileBrowserDialog.dialog('option', 'buttons',
[{
text: 'Ok',
'class': 'btn',
click: function(){
// store the browsed path to the associated text field
callback(currentBrowserPath, options);
$(this).dialog('close');
}
},
{
text: 'Cancel',
'class': 'btn',
click: function(){
$(this).dialog('close');
}
}]);
browse(initialDir, options.url, options.includeFiles);
fileBrowserDialog.dialog('open');
// set up the browser and launch the dialog
var initialDir = '';
if (options.initialDir) {
initialDir = options.initialDir;
}
return false;
};
browse(initialDir, options.url, options.includeFiles);
fileBrowserDialog.dialog('open');
$.fn.fileBrowser = function (options) {
options = $.extend({}, $.Browser.defaults, options);
// text field used for the result
options.field = $(this);
return false;
};
if (options.field.autocomplete && options.autocompleteURL) {
var query = '';
options.field.autocomplete({
position: { my : "top", at: "bottom", collision: "flipfit" },
source: function (request, response) {
//keep track of user submitted search term
query = $.ui.autocomplete.escapeRegex(request.term, options.includeFiles);
$.ajax({
url: options.autocompleteURL,
data: request,
dataType: "json",
success: function (data, item) {
//implement a startsWith filter for the results
var matcher = new RegExp("^" + query, "i");
var a = $.grep(data, function (item, index) {
return matcher.test(item);
});
response(a);
}
});
},
open: function (event, ui) {
$(".ui-autocomplete li.ui-menu-item a").removeClass("ui-corner-all");
$(".ui-autocomplete li.ui-menu-item:odd a").addClass("ui-menu-item-alternate");
}
})
.data("ui-autocomplete")._renderItem = function (ul, item) {
//highlight the matched search term from the item -- note that this is global and will match anywhere
var result_item = item.label;
var x = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "gi");
result_item = result_item.replace(x, function (FullMatch, n) {
return '<b>' + FullMatch + '</b>';
});
return $("<li></li>")
.data("ui-autocomplete-item", item)
.append("<a class='nowrap'>" + result_item + "</a>")
.appendTo(ul);
};
}
$.fn.fileBrowser = function(options){
options = $.extend({}, $.Browser.defaults, options);
// text field used for the result
options.field = $(this);
var initialDir, path, callback, ls = false;
// if the text field is empty and we're given a key then populate it with the last browsed value from localStorage
try { ls = !!(localStorage.getItem); } catch (e) {}
if (ls && options.key) {
path = localStorage['fileBrowser-' + options.key];
}
if (options.key && options.field.val().length == 0 && (path)) {
options.field.val(path);
}
if (options.field.autocomplete && options.autocompleteURL) {
var query = '';
options.field.autocomplete({
position: {my: 'top', at: 'bottom', collision: 'flipfit'},
source: function(request, response){
//keep track of user submitted search term
query = $.ui.autocomplete.escapeRegex(request.term, options.includeFiles);
$.ajax({
url: options.autocompleteURL,
data: request,
dataType: 'json',
success: function(data){
//implement a startsWith filter for the results
var matcher = new RegExp('^' + query, 'i');
var a = $.grep(data, function(item){
return matcher.test(item);
});
response(a);
}
});
},
open: function(){
$('.ui-autocomplete li.ui-menu-item a').removeClass('ui-corner-all');
$('.ui-autocomplete li.ui-menu-item:odd a').addClass('ui-menu-item-alternate');
}
}).data('ui-autocomplete')._renderItem = function(ul, item){
//highlight the matched search term from the item -- note that this is global and will match anywhere
var resultItem = item.label;
var x = new RegExp('(?![^&;]+;)(?!<[^<>]*)(' + query + ')(?![^<>]*>)(?![^&;]+;)', 'gi');
resultItem = resultItem.replace(x, function(fullMatch){
return '<b>' + fullMatch + '</b>';
});
return $('<li></li>')
.data('ui-autocomplete-item', item)
.append('<a class="nowrap">' + resultItem + '</a>')
.appendTo(ul);
};
}
callback = function (path, options) {
// store the browsed path to the associated text field
options.field.val(path);
var path, callback, ls = false;
// if empty text field and given a key then populate it with the last browsed value from localStorage
try { ls = !!(localStorage.getItem); } catch (e) {}
if (ls && options.key) {
path = localStorage['fileBrowser-' + options.key];
}
if (options.key && options.field.val().length == 0 && (path)) {
options.field.val(path);
}
// use a localStorage to remember for next time -- no ie6/7
if (ls && options.key) {
localStorage['fileBrowser-' + options.key] = path;
}
callback = function(path, options){
// store the browsed path to the associated text field
options.field.val(path);
};
// use a localStorage to remember for next time -- no ie6/7
if (ls && options.key) {
localStorage['fileBrowser-' + options.key] = path;
}
};
initialDir = options.field.val() || (options.key && path) || '';
options = $.extend(options, {initialDir: initialDir});
// append the browse button and give it a click behaviour
return options.field.addClass('fileBrowserField').after($('<input type="button" value="Browse&hellip;" class="btn btn-inline fileBrowser" />').click(function () {
$(this).nFileBrowser(callback, options);
return false;
}));
};
options.field.addClass('fileBrowserField');
if (options.showBrowseButton) {
// append the browse button and give it a click behaviour
options.field.after(
$('<input type="button" value="Browse&hellip;" class="btn btn-inline fileBrowser">').on('click',
function(){
$(this).nFileBrowser(callback, $.extend(
{}, options, {initialDir: options.field.val() || (options.key && path) || ''}
));
return false;
}));
}
return options.field;
};
})(jQuery);

View file

@ -1,4 +1,4 @@
$(document).ready(function(){
$(document).ready(function(){
var loading = '<img src="' + sbRoot + '/images/loading16' + themeSpinner + '.gif" height="16" width="16" />';
$('#testGrowl').click(function () {
@ -352,37 +352,162 @@ $(document).ready(function(){
});
});
$('#testTrakt').click(function () {
var trakt_api = $.trim($('#trakt_api').val());
var trakt_username = $.trim($('#trakt_username').val());
var trakt_password = $.trim($('#trakt_password').val());
if (!trakt_api || !trakt_username || !trakt_password) {
$('#testTrakt-result').html('Please fill out the necessary fields above.');
if (!trakt_api) {
$('#trakt_api').addClass('warning');
} else {
$('#trakt_api').removeClass('warning');
}
if (!trakt_username) {
$('#trakt_username').addClass('warning');
} else {
$('#trakt_username').removeClass('warning');
}
if (!trakt_password) {
$('#trakt_password').addClass('warning');
} else {
$('#trakt_password').removeClass('warning');
}
return;
}
$('#trakt_api,#trakt_username,#trakt_password').removeClass('warning');
$(this).prop('disabled', true);
$('#testTrakt-result').html(loading);
$.get(sbRoot + '/home/testTrakt', {'api': trakt_api, 'username': trakt_username, 'password': trakt_password})
.done(function (data) {
$('#testTrakt-result').html(data);
$('#testTrakt').prop('disabled', false);
var elTraktAuth = $('#trakt-authenticate'), elTraktAuthResult = $('#trakt-authentication-result');
function trakt_send_auth(){
var elAccountSelect = $('#trakt_accounts'), strCurAccountId = elAccountSelect.find('option:selected').val(),
elTraktPin = $('#trakt_pin'), strPin = $.trim(elTraktPin.val());
elTraktAuthResult.html(loading);
$.get(sbRoot + '/home/trakt_authenticate', {'pin': strPin, 'account': strCurAccountId})
.done(function(data) {
elTraktAuth.prop('disabled', !1);
elTraktPin.val('');
var JSONData = $.parseJSON(data);
elTraktAuthResult.html('Success' == JSONData.result
? JSONData.result + ' account: ' + JSONData.account_name
: JSONData.result + ' ' + JSONData.error_message);
if ('Success' == JSONData.result) {
var elUpdateRows = $('#trakt-collection').find('tr');
if ('new' == strCurAccountId) {
elAccountSelect.append($('<option>', {value: JSONData.account_id, text: JSONData.account_id + ' - ' + JSONData.account_name + ' (ok)'}));
if ('Connect New Pin' == elUpdateRows.eq(0).find('th').last().text()) {
elUpdateRows.eq(0).find('th').last().html('Account');
elUpdateRows.eq(1).find('th').last().html(JSONData.account_name);
elUpdateRows.eq(1).find('th').last().addClass('tid-' + JSONData.account_id);
elUpdateRows.has('td').each(function(nRow) {
var elCells = $(this).find('td');
if (!(nRow % 2)) {
var IdLoc = 'update_trakt_' + JSONData.account_id + '_' + elCells.eq(0).find('span').attr('data-loc');
elCells.last().html('<input type="checkbox" id="' + IdLoc + '" name="' + IdLoc + '">');
} else {
elCells.attr('colspan', 1);
}
});
}
else
{
elUpdateRows.eq(0).find('th').last().html('Trakt accounts');
elUpdateRows.eq(0).find('th').last().attr('colspan', 1 + parseInt(elUpdateRows.eq(0).find('th').last().attr('colspan'), 10));
elUpdateRows.eq(1).find('th').last().after('<th>' + JSONData.account_name + '</th>');
elUpdateRows.eq(1).find('th').last().addClass('tid-' + JSONData.account_id);
elUpdateRows.has('td').each(function(nRow) {
var elCells = $(this).find('td');
if (!(nRow % 2)) {
var IdLoc = 'update_trakt_' + JSONData.account_id + '_' + elCells.eq(0).find('span').attr('data-loc');
elCells.last().after('<td class="opt"><input type="checkbox" id="' + IdLoc + '" name="' + IdLoc + '"></td>');
} else {
elCells.attr('colspan', 1 + parseInt(elCells.attr('colspan'), 10));
}
});
}
}
else
{
elAccountSelect.find('option[value=' + strCurAccountId + ']').html(JSONData.account_id + ' - ' + JSONData.account_name + ' (ok)');
elUpdateRows.eq(1).find('th[class*="tid-' + JSONData.account_id + '"]').text(JSONData.account_name);
}
}
});
}
elTraktAuth.click(function(e) {
var elTraktPin = $('#trakt_pin');
elTraktPin.removeClass('warning');
if (!$.trim(elTraktPin.val())) {
elTraktPin.addClass('warning');
elTraktAuthResult.html('Please enter a required PIN above.');
} else {
var elAccountSelect = $('#trakt_accounts'), elSelected = elAccountSelect.find('option:selected');
$(this).prop('disabled', !0);
if ('new' != elSelected.val()) {
$.confirm({
'title' : 'Replace Trakt Account',
'message' : 'Are you sure you want to replace <span class="footerhighlight">' + elSelected.text() + '</span> ?<br /><br />',
'buttons' : {
'Yes' : {
'class' : 'green',
'action': function() {
trakt_send_auth();
}
},
'No' : {
'class' : 'red',
'action': function() {
e.preventDefault();
elTraktAuth.prop('disabled', !1);
}
}
}
});
}
else
{
trakt_send_auth();
}
}
});
$('#trakt_accounts').change(function() {
$('#trakt-delete').prop('disabled', 'new' == $('#trakt_accounts').val());
});
$('#trakt-delete').click(function(e) {
var elAccountSelect = $('#trakt_accounts'), elSelected = elAccountSelect.find('option:selected'), that = $(this);
that.prop('disabled', !0);
$.confirm({
'title' : 'Remove Trakt Account',
'message' : 'Are you sure you want to remove <span class="footerhighlight">' + elSelected.text() + '</span> ?<br /><br />',
'buttons' : {
'Yes' : {
'class' : 'green',
'action': function() {
$.get(sbRoot + '/home/trakt_delete', {'accountid': elSelected.val()})
.done(function(data) {
that.prop('disabled', !1);
var JSONData = $.parseJSON(data);
if ('Success' == JSONData.result) {
var elCollection = $('#trakt-collection'), elUpdateRows = elCollection.find('tr'),
header = elCollection.find('th[class*="tid-' + JSONData.account_id + '"]'),
num_acc = parseInt(JSONData.num_accounts, 10);
elUpdateRows.eq(0).find('th').last().html(!num_acc && '<i>Connect New Pin</i>' ||
(1 < num_acc ? 'Trakt accounts' : 'Account'));
elUpdateRows.find('th[colspan]').attr('colspan', 1 < num_acc ? num_acc : 1);
!num_acc && header.html('..') || header.remove();
var elInputs = elUpdateRows.find('input[id*=update_trakt_' + JSONData.account_id + ']');
!num_acc && elInputs.parent().html('..') || elInputs.parent().remove();
elUpdateRows.find('td[colspan]').each(function() {
$(this).attr('colspan', (num_acc ? 1 + num_acc : 2))
});
elSelected.remove();
$('#trakt_accounts').change();
elTraktAuthResult.html('Deleted account: ' + JSONData.account_name);
}
});
}
},
'No' : {
'class' : 'red',
'action': function() {
e.preventDefault();
$('#trakt_accounts').change();
}
}
}
});
});
$('#testEmail').click(function () {

View file

@ -11,7 +11,7 @@ $(document).ready(function () {
function israr_supported() {
var pattern = $('#naming_pattern').val();
$.get(sbRoot + '/config/postProcessing/isRarSupported',
$.get(sbRoot + '/config/postProcessing/isRarSupported',
function (data) {
if (data == "supported") {
} else {
@ -21,11 +21,11 @@ $(document).ready(function () {
});
$('#unpack').qtip('toggle', true);
$('#unpack').css('background-color', '#FFFFDD');
}
});
}
function fill_examples() {
var pattern = $('#naming_pattern').val();
var multi = $('#naming_multi_ep :selected').val();
@ -207,7 +207,7 @@ $(document).ready(function () {
var multi = $('#naming_anime_multi_ep :selected').val();
var anime_type = $('input[name="naming_anime"]:checked').val();
$.get(sbRoot + '/config/postProcessing/testNaming', {pattern: pattern, anime_type: anime_type},
$.get(sbRoot + '/config/postProcessing/testNaming', {pattern: pattern, anime: 'True', anime_type: anime_type},
function (data) {
if (data) {
$('#naming_example_anime').text(data + '.ext');
@ -217,7 +217,7 @@ $(document).ready(function () {
}
});
$.get(sbRoot + '/config/postProcessing/testNaming', {pattern: pattern, multi: multi, anime_type: anime_type},
$.get(sbRoot + '/config/postProcessing/testNaming', {pattern: pattern, multi: multi, anime: 'True', anime_type: anime_type},
function (data) {
if (data) {
$('#naming_example_multi_anime').text(data + '.ext');
@ -227,7 +227,7 @@ $(document).ready(function () {
}
});
$.get(sbRoot + '/config/postProcessing/isNamingValid', {pattern: pattern, multi: multi, anime_type: anime_type},
$.get(sbRoot + '/config/postProcessing/isNamingValid', {pattern: pattern, multi: multi, anime: 'True', anime_type: anime_type},
function (data) {
if (data == "invalid") {
$('#naming_anime_pattern').qtip('option', {

View file

@ -80,7 +80,6 @@ $(document).ready(function(){
$(torrent_seed_time_option).show();
} else if ('transmission' == selectedProvider){
client = 'Transmission';
$(torrent_seed_time_option).show();
$(torrent_high_bandwidth_option).show();
$(torrent_label_option).hide();
//$('#directory_title').text(client + directory);

106
gui/slick/js/editShow.js Normal file
View file

@ -0,0 +1,106 @@
/*globals $, config, sbRoot, generate_bwlist*/
$(document).ready(function () {
$.getJSON(sbRoot + '/home/addShows/getIndexerLanguages', {}, function (data) {
var resultStr, flag, selected, current_lang_added = '';
if (data.results.length === 0) {
flag = ' class="flag" style="background-image:url(' + sbRoot + '/images/flags/' + config.show_lang + '.png)"';
resultStr = '<option value="' + config.show_lang + '" selected="selected"' + flag + '>' + config.show_lang + '</option>';
} else {
current_lang_added = false;
$.each(data.results, function (index, obj) {
if (obj === config.show_lang) {
selected = ' selected="selected"';
current_lang_added = true;
}
else {
selected = '';
}
flag = ' class="flag" style="background-image:url(' + sbRoot + '/images/flags/' + obj + '.png);"';
resultStr += '<option value="' + obj + '"' + selected + flag + '>' + obj + '</option>';
});
if (!current_lang_added) {
resultStr += '<option value=" ' + config.show_lang + '" selected="selected"> ' + config.show_lang + '</option>';
}
}
$('#indexerLangSelectEdit').html(resultStr);
});
var all_exceptions = [];
$('#location').fileBrowser({title: 'Select Show Location'});
$('#submit').click(function () {
all_exceptions = [];
$('#exceptions_list').find('option').each (function () {
all_exceptions.push($(this).val());
});
$('#exceptions_list').val(all_exceptions);
if (config.show_isanime) {
generate_bwlist();
}
});
$('#addSceneName').click(function () {
var scene_ex = $('#SceneName').val();
var scene_ex_season = $('#SceneNameSeason').val();
var option = $('<option>');
all_exceptions = [];
$('#exceptions_list').find('option').each (function () {
all_exceptions.push($(this).val());
});
$('#SceneName').val('');
$('#SceneNameSeason').val('');
if ($.inArray(scene_ex_season + '|' + scene_ex, all_exceptions) > -1 || (scene_ex === '')) {
return;
}
$('#SceneException').show();
option.attr('value', scene_ex_season + '|' + scene_ex);
if (scene_ex_season === "-1") {
option.html('S*: ' + scene_ex);
}
else {
option.html('S' + scene_ex_season + ': ' + scene_ex);
}
return option.appendTo('#exceptions_list');
});
$('#removeSceneName').click(function () {
$('#exceptions_list').find('option:selected').remove();
$(this).toggle_SceneException();
});
$.fn.toggle_SceneException = function () {
all_exceptions = [];
$('#exceptions_list').find('option').each (function () {
all_exceptions.push($(this).val());
});
if ('' === all_exceptions) {
$('#SceneException').hide();
}
else {
$('#SceneException').show();
}
};
$(this).toggle_SceneException();
});

View file

@ -191,8 +191,9 @@ FormToWizard.prototype = {
$section.data('elements', []);
//create each 'step' DIV and add it to main Steps Container:
var $stepwords = ['first', 'second', 'third'], $thestep = $('<div class="step disabledstep" />').data('section', i).html(($stepwords[i]
+ ' step') + '<div class="smalltext">' + $section.find('legend:eq(0)').text() + '<p></p></div>').appendTo($stepsguide);
var $stepwords = ['first', 'then', 'finally'],
$thestep = $('<div class="step disabledstep" />').data('section', i).html(($stepwords[i]) +
'<div class="smalltext">' + $section.find('legend:eq(0)').text() + '<p></p></div>').appendTo($stepsguide);
//assign behavior to each step div
$thestep.click(function(){

141
gui/slick/js/home.js Normal file
View file

@ -0,0 +1,141 @@
$.tablesorter.addParser({
id: 'loadingNames',
is: function (s) {
return false;
},
format: function (s) {
if (s.indexOf('Loading...') === 0) {
return s.replace('Loading...', '000');
} else if (config.sortArticle) {
return (s || '');
} else {
return (s || '').replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1');
}
},
type: 'text'
});
$.tablesorter.addParser({
id: 'quality',
is: function (s) {
return false;
},
format: function (s) {
return s.replace('hd1080p', 5).replace('hd720p', 4).replace('hd', 3).replace('sd', 2).replace('any', 1).replace('custom', 7);
},
type: 'numeric'
});
$(document).ready(function () {
if (config.homeSearchFocus) {
$('#search_show_name').focus();
}
if (config.fuzzyDating) {
fuzzyMoment({
dtInline: config.isPoster,
containerClass: config.fuzzydate,
dateHasTime: !1,
dateFormat: config.datePreset,
timeFormat: config.timePreset,
trimZero: config.trimZero
});
}
$('div[id^="progressbar"]').each(function (k, v) {
var progress = parseInt($(this).siblings('span[class="sort-data"]').attr('data-progress'), 10), elId = '#' + $(this).attr('id'), v = 80;
$(elId).progressbar({value: progress});
if (progress < 80) {
v = progress >= 40 ? 60 : (progress >= 20 ? 40 : 20);
}
$(elId + ' > .ui-progressbar-value').addClass('progress-' + v);
});
$('img#network').on('error', function () {
$(this).parent().text($(this).attr('alt'));
$(this).remove();
});
if (config.isPoster) {
$('.container').each(function (i, obj) {
$(obj).isotope({
itemSelector: '.show',
sortBy: config.posterSortby,
sortAscending: config.posterSortdir,
layoutMode: 'masonry',
masonry: {
columnWidth: 12,
isFitWidth: true
},
getSortData: {
name: function (itemElem) {
var name = $(itemElem).attr('data-name');
if (config.sortArticle) {
return (name || '');
} else {
return (name || '').replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1');
}
},
date: function (itemElem) {
var date = $(itemElem).attr('data-date');
return date.length && parseInt(date, 10) || Number.POSITIVE_INFINITY;
},
network: '[data-network]',
progress: function (itemElem) {
var progress = $(itemElem).children('.sort-data').attr('data-progress');
return progress.length && parseInt(progress, 10) || Number.NEGATIVE_INFINITY;
}
}
});
$('#postersort').on('change', function () {
var sortValue = this.value;
$(obj).isotope({sortBy: sortValue});
$.get(this.options[this.selectedIndex].getAttribute('data-sort'));
});
$('#postersortdirection').on('change', function () {
var sortDirection = this.value;
sortDirection = sortDirection == 'true';
$(obj).isotope({sortAscending: sortDirection});
$.get(this.options[this.selectedIndex].getAttribute('data-sort'));
});
});
} else {
$('.tablesorter').each(function (i, obj) {
$(obj).has('tbody tr').tablesorter({
sortList: [[5, 1], [1, 0]],
textExtraction: {
0: function (node) {
return $(node).find('span').text().toLowerCase();
},
2: function (node) {
return $(node).find('span').text().toLowerCase();
},
3: function (node) {
return $(node).find('span').text().toLowerCase();
},
4: function (node) {
return $(node).find('span').attr('data-progress');
},
5: function (node) {
return $(node).find('i').attr('alt');
}
},
widgets: ['saveSort', 'zebra', 'stickyHeaders', 'filter'],
headers: {
0: {sorter: 'isoDate'},
1: {sorter: 'loadingNames'},
3: {sorter: 'quality'},
4: {sorter: 'eps'}
},
widgetOptions: {
filter_columnFilters: false,
filter_reset: '.resetshows'
},
sortStable: true
});
$.tablesorter.filter.bindSearch($(obj), $('.search'));
});
}
});

40
gui/slick/js/inc_top.js Normal file
View file

@ -0,0 +1,40 @@
function initActions() {
$('#SubMenu a[href*="/home/restart/"]').addClass('btn restart').html('<i class="sgicon-restart"></i>Restart');
$('#SubMenu a[href*="/home/shutdown/"]').addClass('btn shutdown').html('<i class="sgicon-shutdown"></i>Shutdown');
$('#SubMenu a[href*="/home/logout/"]').addClass('btn').html('<i class="sgicon-logout"></i>Logout');
$('#SubMenu a:contains("Edit")').addClass('btn').html('<i class="sgicon-edit"></i>Edit');
$('#SubMenu a:contains("Remove")').addClass('btn remove').html('<i class="sgicon-delete"></i>Remove');
$('#SubMenu a:contains("Clear History")').addClass('btn clearhistory').html('<i class="sgicon-delete"></i>Clear History');
$('#SubMenu a:contains("Trim History")').addClass('btn trimhistory').html('<i class="sgicon-trim"></i>Trim History');
$('#SubMenu a[href$="/errorlogs/clearerrors/"]').addClass('btn').html('<i class="sgicon-delete"></i>Clear Errors');
$('#SubMenu a:contains("Re-scan")').addClass('btn').html('<i class="sgicon-refresh"></i>Re-scan');
$('#SubMenu a:contains("Backlog Overview")').addClass('btn').html('<i class="sgicon-backlog"></i>Backlog Overview');
$('#SubMenu a[href$="/home/updatePLEX/"]').addClass('btn').html('<i class="sgicon-plex"></i>Update PLEX');
$('#SubMenu a:contains("Force")').addClass('btn').html('<i class="sgicon-fullupdate"></i>Force Full Update');
$('#SubMenu a:contains("Rename")').addClass('btn').html('<i class="sgicon-rename"></i>Media Renamer');
$('#SubMenu a[href$="/config/subtitles/"]').addClass('btn').html('<i class="sgicon-subtitles"></i>Search Subtitles');
$('#SubMenu a[href*="/home/subtitleShow"]').addClass('btn').html('<i class="sgicon-subtitles"></i>Download Subtitles');
$('#SubMenu a:contains("Anime")').addClass('btn').html('<i class="sgicon-anime"></i>Anime');
$('#SubMenu a:contains("Settings")').addClass('btn').html('<i class="sgicon-search"></i>Search Settings');
$('#SubMenu a:contains("Provider")').addClass('btn').html('<i class="sgicon-search"></i>Search Providers');
$('#SubMenu a:contains("General")').addClass('btn').html('<i class="sgicon-config"></i>General');
$('#SubMenu a:contains("Episode Status")').addClass('btn').html('<i class="sgicon-episodestatus"></i>Episode Status Management');
$('#SubMenu a:contains("Missed Subtitle")').addClass('btn').html('<i class="sgicon-subtitles"></i>Missed Subtitles');
$('#SubMenu a[href$="/home/addShows/"]').addClass('btn').html('<i class="sgicon-addshow"></i>Add Show');
$('#SubMenu a:contains("Processing")').addClass('btn').html('<i class="sgicon-postprocess"></i>Post-Processing');
$('#SubMenu a:contains("Manage Searches")').addClass('btn').html('<i class="sgicon-search"></i>Manage Searches');
$('#SubMenu a:contains("Manage Torrents")').addClass('btn').html('<i class="sgicon-bittorrent"></i>Manage Torrents');
$('#SubMenu a:contains("Show Queue Overview")').addClass('btn').html('<i class="sgicon-showqueue"></i>Show Queue Overview');
$('#SubMenu a[href$="/manage/failedDownloads/"]').addClass('btn').html('<i class="sgicon-failed"></i>Failed Downloads');
$('#SubMenu a:contains("Notification")').addClass('btn').html('<i class="sgicon-notification"></i>Notifications');
$('#SubMenu a:contains("Update show in XBMC")').addClass('btn').html('<i class="sgicon-xbmc"></i>Update show in XBMC');
$('#SubMenu a[href$="/home/updateXBMC/"]').addClass('btn').html('<i class="sgicon-xbmc"></i>Update XBMC');
$('#SubMenu a:contains("Update show in Kodi")').addClass('btn').html('<i class="sgicon-kodi"></i>Update show in Kodi');
$('#SubMenu a[href$="/home/updateKODI/"]').addClass('btn').html('<i class="sgicon-kodi"></i>Update Kodi');
}
$(document).ready(function(){
initActions();
$('#NAV' + topmenu).addClass('active');
$('.dropdown-toggle').dropdownHover();
});

File diff suppressed because one or more lines are too long

View file

@ -77,40 +77,38 @@ $(document).ready(function () {
if (0 === data.results.length) {
resultStr += '<span class="boldest">Sorry, no results found. Try a different search.</span>';
} else {
var idxSrcDB = 0, idxSrcDBId = 1, idxSrcUrl = 2, idxShowID = 3, idxTitle = 4, idxDate = 5;
$.each(data.results, function (index, obj) {
checked = (0 == row ? ' checked' : '');
rowType = (0 == row % 2 ? '' : ' class="alt"');
row++;
var whichSeries = cleanseText(obj.join('|'), !0),
display_show_name = cleanseText(obj[4], !0),
showstartdate = '';
var display_show_name = cleanseText(obj[idxTitle], !0), showstartdate = '';
if (null !== obj[5]) {
var startDate = new Date(obj[5]);
if (null !== obj[idxDate]) {
var startDate = new Date(obj[idxDate]);
var today = new Date();
showstartdate = '&nbsp;<span class="stepone-result-date">('
+ (startDate > today ? 'will debut' : 'started')
+ ' on ' + obj[5] + ')</span>';
+ ': ' + obj[idxDate] + ')</span>';
}
resultStr += '<div' + rowType + '>'
+ '<input id="whichSeries" type="radio"'
+ ' class="stepone-result-radio"'
+ ' title="Add show <span style=\'color: rgb(66, 139, 202)\'>' + display_show_name + '</span>"'
+ ' name="whichSeries"'
+ ' value="' + whichSeries + '"'
+ ' value="' + cleanseText([obj[idxSrcDBId], obj[idxSrcDB], obj[idxShowID], obj[idxTitle]].join('|'), !0) + '"'
+ checked
+ ' />'
+ '<a'
+ ' class="stepone-result-title"'
+ ' title="View detail for <span style=\'color: rgb(66, 139, 202)\'>' + display_show_name + '</span>"'
+ ' href="' + anonURL + obj[2] + obj[3] + ((data.langid && '' != data.langid) ? '&lid=' + data.langid : '') + '"'
+ ' href="' + anonURL + obj[idxSrcUrl] + obj[idxShowID] + ((data.langid && '' != data.langid) ? '&lid=' + data.langid : '') + '"'
+ ' onclick="window.open(this.href, \'_blank\'); return false;"'
+ '>' + display_show_name + '</a>'
+ showstartdate
+ (null == obj[0] ? ''
: '&nbsp;<span class="stepone-result-db grey-text">' + '[' + obj[0] + ']' + '</span>')
+ (null == obj[idxSrcDB] ? ''
: '&nbsp;<span class="stepone-result-db grey-text">' + '[' + obj[idxSrcDB] + ']' + '</span>')
+ '</div>' + "\n";
});
}
@ -189,22 +187,24 @@ $(document).ready(function () {
function updateSampleText() {
// if something's selected then we have some behavior to figure out
var show_name,
var show_name = '',
sep_char,
elRadio = $('input:radio[name="whichSeries"]:checked'),
elInput = $('input:hidden[name="whichSeries"]'),
elScene = $('#scene'),
elRootDirs = $('#rootDirs'),
elFullShowPath = $('#fullShowPath');
elFullShowPath = $('#fullShowPath'),
idxWhichShowID = 2, idxWhichTitle = 3;
// if they've picked a radio button then use that
if (elRadio.length) {
show_name = elRadio.val().split('|')[4];
show_name = elRadio.val().split('|')[idxWhichTitle];
elScene[0].checked = 0 <= show_scene_maps.indexOf(parseInt(elRadio.val().split('|')[idxWhichShowID], 10));
$('#scene-maps-found').css('display', elScene.is(':checked') ? 'inline' : 'None');
}
// 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 = '';
}
update_bwlist(show_name);
var sample_text = '<p>Adding show <span class="show-name">' + cleanseText(show_name, !0) + '</span>'

View file

@ -1,22 +1,24 @@
function setFromPresets (preset) {
var elCustomQuality = $('.show-if-quality-custom'),
selected = 'selected';
if (0 == preset) {
if (preset = parseInt(preset)) {
elCustomQuality.hide();
var upgrade = !0;
$('#anyQualities, #bestQualities').find('option').each(function() {
if (upgrade && 'bestQualities' === $(this).parent().attr('id')) {
upgrade = !1;
switch (preset) {
case 3: preset = 128 + 32 + 4; break;
case 164: preset = 256 + 64 + 16 + 4; break;
case 336: preset = 256; break;
default: preset = 0;
}
}
$(this).attr(selected, ((preset & parseInt($(this).val())) ? selected : false));
});
} else
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() {
@ -24,7 +26,7 @@ $(document).ready(function() {
selected = ':selected';
elQualityPreset.change(function() {
setFromPresets($('#qualityPreset').find(selected).val());
setFromPresets($(this).find(selected).val());
});
setFromPresets(elQualityPreset.find(selected).val());

View file

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

View file

@ -1,15 +1,15 @@
#!/bin/sh
#
### BEGIN INIT INFO
# Provides: sickbeard
# Provides: sickgear
# Required-Start: $local_fs $network $remote_fs
# Required-Stop: $local_fs $network $remote_fs
# Should-Start: $NetworkManager
# Should-Stop: $NetworkManager
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts instance of SickBeard
# Description: starts instance of SickBeard using start-stop-daemon
# Short-Description: starts instance of SickGear
# Description: starts instance of SickGear using start-stop-daemon
### END INIT INFO
# Load the VERBOSE setting and other rcS variables
@ -19,57 +19,57 @@
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
# Source SickBeard configuration
if [ -f /etc/default/sickbeard ]; then
. /etc/default/sickbeard
# Source SickGear configuration
if [ -f /etc/default/sickgear ]; then
. /etc/default/sickgear
else
[ "${VERBOSE}" != no ] && echo "/etc/default/sickbeard not found. Using default settings.";
[ "${VERBOSE}" != no ] && echo "/etc/default/sickgear not found. Using default settings.";
fi
## Don't set -e
## Don't edit this file!
## Edit user configuation in /etc/default/sickbeard to change
## Edit user configuation in /etc/default/sickgear to change
##
## SB_USER= #$RUN_AS, username to run sickbeard under, the default is sickbeard
## SB_GROUP= #$RUN_GROUP, group to run sickbeard under, the default is sickbeard
## SB_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickbeard
## SB_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickbeard
## SB_PIDFILE= #$PID_FILE, the location of sickbeard.pid, the default is /var/run/sickbeard/sickbeard.pid
## SG_USER= #$RUN_AS, username to run sickgear under, the default is sickgear
## SG_GROUP= #$RUN_GROUP, group to run sickgear under, the default is sickgear
## SG_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickgear
## SG_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickgear
## SG_PIDFILE= #$PID_FILE, the location of sickgear.pid, the default is /var/run/sickgear/sickgear.pid
## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python
## SB_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for sickbeard, i.e. " --config=/home/sickbeard/config.ini"
## SG_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for sickgear, i.e. " --config=/home/sickgear/config.ini"
## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users"
##
## EXAMPLE if want to run as different user
## add SB_USER=username to /etc/default/sickbeard
## otherwise default sickbeard is used
## add SG_USER=username to /etc/default/sickgear
## otherwise default sickgear is used
# Script name
NAME=$(basename "$0")
# App name
DESC=SickBeard
DESC=SickGear
## The defaults
# Run as username
RUN_AS=${SB_USER-sickbeard}
# Run as username
RUN_AS=${SG_USER-sickgear}
# Run as group
RUN_GROUP=${SB_GROUP-sickbeard}
RUN_GROUP=${SG_GROUP-sickgear}
# Path to app SB_HOME=path_to_app_SickBeard.py
APP_PATH=${SB_HOME-/opt/sickbeard}
# Path to app SG_HOME=path_to_app_SickBeard.py
APP_PATH=${SG_HOME-/opt/sickgear}
# Data directory where sickbeard.db, cache and logs are stored
DATA_DIR=${SB_DATA-/opt/sickbeard}
DATA_DIR=${SG_DATA-/opt/sickgear}
# Path to store PID file
PID_FILE=${SB_PIDFILE-/var/run/sickbeard/sickbeard.pid}
PID_FILE=${SG_PIDFILE-/var/run/sickgear/sickgear.pid}
# path to python bin
DAEMON=${PYTHON_BIN-/usr/bin/python}
# Extra daemon option like: SB_OPTS=" --config=/home/sickbeard/config.ini"
EXTRA_DAEMON_OPTS=${SB_OPTS-}
# Extra daemon option like: SG_OPTS=" --config=/home/sickgear/config.ini"
EXTRA_DAEMON_OPTS=${SG_OPTS-}
# Extra start-stop-daemon option like START_OPTS=" --group=users"
EXTRA_SSD_OPTS=${SSD_OPTS-}
@ -82,7 +82,7 @@ DAEMON_OPTS=" SickBeard.py -q --daemon --nolaunch --pidfile=${PID_FILE} --datadi
test -x $DAEMON || exit 0
# Create PID directory if not exist and ensure the SickBeard user can write to it
# Create PID directory if not exist and ensure the SickGear user can write to it
if [ ! -d $PID_PATH ]; then
mkdir -p $PID_PATH
chown $RUN_AS $PID_PATH
@ -101,9 +101,9 @@ if [ -e $PID_FILE ]; then
fi
fi
start_sickbeard() {
start_sickgear() {
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
start-stop-daemon -d $APP_PATH -c $RUN_AS --group=${RUN_GROUP} $EXTRA_SSD_OPTS --start --quiet --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS
start-stop-daemon -d $APP_PATH -c $RUN_AS --group=${RUN_GROUP} $EXTRA_SSD_OPTS --start --quiet --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS
RETVAL="$?"
case "${RETVAL}" in
# Service was started or was running already
@ -115,7 +115,7 @@ start_sickbeard() {
return 0
}
stop_sickbeard() {
stop_sickgear() {
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
start-stop-daemon --stop --pidfile $PID_FILE --quiet --retry TERM/30/KILL/5
RETVAL="$?"
@ -127,23 +127,23 @@ stop_sickbeard() {
esac
[ "${RETVAL}" = 2 ] && return 2
[ -f "${PID_FILE}" ] && rm -f ${PID_FILE}
return 0
return 0
}
case "$1" in
start)
start_sickbeard
start_sickgear
exit $?
;;
stop)
stop_sickbeard
stop_sickgear
exit $?
;;
restart|force-reload)
stop_sickbeard
stop_sickgear
sleep 2
start_sickbeard
start_sickgear
return $?
;;
status)

View file

@ -1,7 +1,7 @@
#!/bin/sh
#
### BEGIN INIT INFO
# Provides: sickbeard
# Provides: sickgear
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
@ -13,27 +13,27 @@
# Source function library.
. /etc/init.d/functions
# Source SickBeard configuration
if [ -f /etc/sysconfig/sickbeard ]; then
. /etc/sysconfig/sickbeard
# Source SickGear configuration
if [ -f /etc/sysconfig/sickgear ]; then
. /etc/sysconfig/sickgear
fi
prog=sickbeard
prog=sickgear
lockfile=/var/lock/subsys/$prog
## Edit user configuation in /etc/sysconfig/sickbeard to change
## Edit user configuation in /etc/sysconfig/sickgear to change
## the defaults
username=${SB_USER-sickbeard}
homedir=${SB_HOME-/opt/sickbeard}
datadir=${SB_DATA-/opt/sickbeard}
pidfile=${SB_PIDFILE-/var/run/sickbeard/sickbeard.pid}
nice=${SB_NICE-}
username=${SG_USER-sickgear}
homedir=${SG_HOME-/opt/sickgear}
datadir=${SG_DATA-/opt/sickgear}
pidfile=${SG_PIDFILE-/var/run/sickgear/sickgear.pid}
nice=${SG_NICE-}
##
pidpath=`dirname ${pidfile}`
options=" --daemon --nolaunch --pidfile=${pidfile} --datadir=${datadir}"
# create PID directory if not exist and ensure the SickBeard user can write to it
# create PID directory if not exist and ensure the SickGear user can write to it
if [ ! -d $pidpath ]; then
mkdir -p $pidpath
chown $username $pidpath

98
init-scripts/init.freebsd Executable file
View file

@ -0,0 +1,98 @@
#!/bin/sh
#
# PROVIDE: sickgear
# REQUIRE: LOGIN
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
# to enable this service:
#
# sickgear_enable (bool): Set to NO by default.
# Set it to YES to enable it.
# sickgear_user: The user account SickGear daemon runs as what
# you want it to be. It uses '_sabnzbd' user by
# default. Do not sets it as empty or it will run
# as root.
# sickgear_dir: Directory where SickGear lives.
# Default: /usr/local/sickgear
# sickgear_chdir: Change to this directory before running SickGear.
# Default is same as sickgear_dir.
# sickgear_datadir: Data directory for Sick Beard (DB, Logs, config)
# Default is same as sickgear_chdir
# sickgear_pid: The name of the pidfile to create.
# Default is sickgear.pid in sickgear_dir.
# sickgear_host: The hostname or IP SickGear is listening on
# Default is 127.0.0.1
# sickgear_port: The port SickGear is listening on
# Default is 8081
# sickgear_web_user: Username to authenticate to the SickGear web interface
# Default is an empty string (no username)
# sickgear_web_password: Password to authenticate to the SickGear web interface
# Default is an empty string (no password)
# sickgear_webroot: Set to value of web_root in config (for proxies etc)
# Default is an empty string (if set must start with a "/")
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
. /etc/rc.subr
name="sickgear"
rcvar=${name}_enable
load_rc_config ${name}
: ${sickgear_enable:="NO"}
: ${sickgear_user:="_sabnzbd"}
: ${sickgear_dir:="/usr/local/sickgear"}
: ${sickgear_chdir:="${sickgear_dir}"}
: ${sickgear_datadir:="${sickgear_chdir}"}
: ${sickgear_pid:="${sickgear_dir}/sickgear.pid"}
: ${sickgear_host:="127.0.0.1"}
: ${sickgear_port:="8081"}
: ${sickgear_web_user:=""}
: ${sickgear_web_password:=""}
: ${sickgear_webroot:=""}
status_cmd="${name}_status"
stop_cmd="${name}_stop"
command="/usr/sbin/daemon"
command_args="-f -p ${sickgear_pid} python ${sickgear_dir}/SickBeard.py --quiet --nolaunch"
# Add datadir to the command if set
[ ! -z "${sickgear_datadir}" ] && \
command_args="${command_args} --datadir ${sickgear_datadir}"
# Ensure user is root when running this script.
if [ `id -u` != "0" ]; then
echo "Oops, you should be root before running this!"
exit 1
fi
verify_sickgear_pid() {
# Make sure the pid corresponds to the SickGear process.
pid=`cat ${sickgear_pid} 2>/dev/null`
ps -p ${pid} 2>/dev/null | grep -q "python ${sickgear_dir}/SickBeard.py"
return $?
}
# Try to stop SickGear cleanly by calling shutdown over http.
sickgear_stop() {
echo "Stopping $name"
verify_sickgear_pid
sickgear_url="${sickgear_host}:${sickgear_port}"
[ ! -z "${sickgear_web_user}" ] && \
sickgear_url="${sickgear_web_user}:${sickgear_web_password}@${sickgear_url}"
[ ! -z "${sickgear_webroot}" ] && \
sickgear_url="${sickgear_url}${sickgear_webroot}"
fetch -o - -q "http://${sickgear_url}/home/shutdown/?pid=${pid}" >/dev/null
if [ -n "${pid}" ]; then
wait_for_pids ${pid}
echo "Stopped"
fi
}
sickgear_status() {
verify_sickgear_pid && echo "$name is running as ${pid}" || echo "$name is not running"
}
run_rc_command "$1"

View file

@ -9,15 +9,15 @@
# You will need to create a configuration file in order for this script
# to work properly. Please create /etc/conf.d/sickbeard with the following:
#
# SICKBEARD_USER=<user you want sickbeard to run under>
# SICKBEARD_GROUP=<group you want sickbeard to run under>
# SICKBEARD_DIR=<path to Sickbeard.py>
# SICKGEAR_USER=<user you want sickgear to run under>
# SICKGEAR_GROUP=<group you want sickgear to run under>
# SICKGEAR_DIR=<path to Sickbeard.py>
# PATH_TO_PYTHON_2=/usr/bin/python2
# SICKBEARD_DATADIR=<directory that contains sickbeard.db file>
# SICKBEARD_CONFDIR=<directory that contains Sickbeard's config.ini file>
# SICKGEAR_DATADIR=<directory that contains sickbeard.db file>
# SICKGEAR_CONFDIR=<directory that contains SickGear's config.ini file>
#
RUNDIR=/var/run/sickbeard
RUNDIR=/var/run/sickgear
depend() {
need net
@ -30,32 +30,32 @@ get_pidfile() {
-e 's/[[:space:]]*$//' \
-e 's/^[[:space:]]*//' \
-e "s/^\(.*\)=\([^\"']*\)$/\1=\"\2\"/" \
< ${SICKBEARD_CONFDIR}/config.ini \
< ${SICKGEAR_CONFDIR}/config.ini \
| sed -n -e "/^\[General\]/,/^\s*\[/{/^[^;].*\=.*/p;}"`
echo "${RUNDIR}/sickbeard-${web_port}.pid"
echo "${RUNDIR}/sickgear-${web_port}.pid"
}
start() {
ebegin "Starting Sickbeard"
ebegin "Starting SickGear"
checkpath -q -d -o ${SICKBEARD_USER}:${SICKBEARD_GROUP} -m 0770 "${RUNDIR}"
checkpath -q -d -o ${SICKGEAR_USER}:${SICKGEAR_GROUP} -m 0770 "${RUNDIR}"
start-stop-daemon \
--quiet \
--start \
--user ${SICKBEARD_USER} \
--group ${SICKBEARD_GROUP} \
--name sickbeard \
--user ${SICKGEAR_USER} \
--group ${SICKGEAR_GROUP} \
--name sickgear \
--background \
--pidfile $(get_pidfile) \
--exec ${PATH_TO_PYTHON_2} \
-- \
${SICKBEARD_DIR}/SickBeard.py \
${SICKGEAR_DIR}/SickBeard.py \
-d \
--pidfile $(get_pidfile) \
--config ${SICKBEARD_CONFDIR}/config.ini \
--datadir ${SICKBEARD_DATADIR}
--config ${SICKGEAR_CONFDIR}/config.ini \
--datadir ${SICKGEAR_DATADIR}
eend $?
}
@ -74,5 +74,5 @@ stop() {
local pidfile=$(get_pidfile)
local rc
ebegin "Stopping Sickbeard"
ebegin "Stopping SickGear"
}

View file

@ -1,35 +1,35 @@
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<!--
Assumes user=sickbeard group=other
Assumes /opt/sickbeard is installation directory
Assumes user=sickgear group=other
Assumes /opt/sickgear is installation directory
See http://www.sun.com/bigadmin/content/selfheal/sdev_intro.jsp for more information
To install (see http://docs.sun.com/app/docs/doc/819-2379/fgour?l=en&a=view for more information)
svccfg import sickbeard.smf
svccfg import sickgear.smf
To enable/disable
svcadm enable sickbeard
svcadm disable sickbeard
svcadm enable sickgear
svcadm disable sickgear
To check if failures
svcs -xv
To check logs
tail /var/svc/log/network-sickbeard\:default.log
tail /var/svc/log/network-sickgear\:default.log
-->
<service_bundle type='manifest' name='sickbeard'>
<service_bundle type='manifest' name='sickgear'>
<service
name='network/sickbeard'
name='network/sickgear'
type='service'
version='1'>
<create_default_instance enabled='false' />
<single_instance />
<!--
Only start in muti-user mode
-->
@ -39,7 +39,7 @@
type='service'>
<service_fmri value='svc:/milestone/multi-user' />
</dependency>
<!--
Wait for network interfaces to be initialized.
-->
@ -49,7 +49,7 @@
type='service'>
<service_fmri value='svc:/milestone/network:default'/>
</dependency>
<!--
Wait for all local filesystems to be mounted.
-->
@ -59,37 +59,37 @@
type='service'>
<service_fmri value='svc:/system/filesystem/local:default'/>
</dependency>
<!-- execute as user sickbeard -->
<!-- execute as user sickgear -->
<method_context>
<method_credential user='sickbeard' group='other' />
<method_credential user='sickgear' group='other' />
</method_context>
<exec_method
type='method'
name='start'
exec='/opt/sickbeard/SickBeard.py --daemon'
exec='/opt/sickgear/SickBeard.py --daemon'
timeout_seconds='60'>
</exec_method>
<exec_method
type='method'
name='stop'
exec=':kill'
timeout_seconds='2'>
</exec_method>
<template>
<common_name>
<loctext xml:lang='C'>Sickbeard</loctext>
<loctext xml:lang='C'>SickGear</loctext>
</common_name>
<documentation>
<doc_link name='sickbeard'
uri='http://www.sickbeard.com/' />
<doc_link name='sickgear'
uri='https://github.com/SickGear/SickGear/' />
</documentation>
</template>
</service>
</service_bundle>

View file

@ -4,54 +4,54 @@
#
# - Option names (e.g. ExecStart=, Type=) are case-sensitive)
#
# - Adjust User= and Group= to the user/group you want Sickbeard to run as.
# - Adjust User= and Group= to the user/group you want SickGear to run as.
#
# - Optional adjust EnvironmentFile= path to configuration file
# Can ONLY be used for configuring extra options used in ExecStart.
# Putting a minus (-) in front of file means no error warning if the file doesn't exist
#
# - Adjust ExecStart= to point to your python and SickBeard executables.
# - Adjust ExecStart= to point to your python and SickGear executables.
# The FIRST token of the command line must be an ABSOLUTE FILE NAME,
# then followed by arguments for the process.
# If no --datadir is given, data is stored in same dir as SickBeard.py
# Arguments can also be set in EnvironmentFile (except python)
#
# - WantedBy= specifies which target (i.e. runlevel) to start Sickbeard for.
# - WantedBy= specifies which target (i.e. runlevel) to start SickGear for.
# multi-user.target equates to runlevel 3 (multi-user text mode)
# graphical.target equates to runlevel 5 (multi-user X11 graphical mode)
#
### Example Using SickBeard as daemon with pid file
### Example Using SickGear as daemon with pid file
# Type=forking
# PIDFile=/var/run/sickbeard/sickbeard.pid
# ExecStart=/usr/bin/python /opt/sickbeard/SickBeard.py -q --daemon --nolaunch --pidfile=/var/run/sickbeard/sickbeard.pid --datadir=/opt/sickbeard
# PIDFile=/var/run/sickgear/sickgear.pid
# ExecStart=/usr/bin/python /opt/sickgear/SickBeard.py -q --daemon --nolaunch --pidfile=/var/run/sickgear/sickgear.pid --datadir=/opt/sickgear
## Example Using SickBeard as daemon without pid file
## Example Using SickGear as daemon without pid file
# Type=forking
# GuessMainPID=no
# ExecStart=/usr/bin/python /opt/sickbeard/SickBeard.py -q --daemon --nolaunch --datadir=/opt/sickbeard
# ExecStart=/usr/bin/python /opt/sickgear/SickBeard.py -q --daemon --nolaunch --datadir=/opt/sickgear
### Example Using simple
# Type=simple
# ExecStart=/usr/bin/python /opt/sickbeard/SickBeard.py -q --nolaunch
# ExecStart=/usr/bin/python /opt/sickgear/SickBeard.py -q --nolaunch
### Example Using simple with EnvironmentFile where SB_DATA=/home/sickbeard/.sickbeard in /etc/sickbeard.conf
### Example Using simple with EnvironmentFile where SB_DATA=/home/sickgear/.sickgear in /etc/sickgear.conf
# Type=simple
# EnvironmentFile=/etc/sickbeard.conf
# ExecStart=/usr/bin/python /opt/sickbeard/SickBeard.py -q --nolaunch --datadir=${SB_DATA}
# EnvironmentFile=/etc/sickgear.conf
# ExecStart=/usr/bin/python /opt/sickgear/SickBeard.py -q --nolaunch --datadir=${SB_DATA}
### Configuration
[Unit]
Description=SickBeard Daemon
Description=SickGear Daemon
[Service]
User=sickbeard
Group=sickbeard
User=sickgear
Group=sickgear
Type=forking
GuessMainPID=no
ExecStart=/usr/bin/python /opt/sickbeard/SickBeard.py -q --daemon --nolaunch --datadir=/opt/sickbeard
ExecStart=/usr/bin/python /opt/sickgear/SickBeard.py -q --daemon --nolaunch --datadir=/opt/sickgear
[Install]
WantedBy=multi-user.target

View file

@ -1,7 +1,7 @@
#!/bin/sh
#
### BEGIN INIT INFO
# Provides: sickbeard
# Provides: sickgear
# Required-Start: $local_fs $network $remote_fs
# Required-Stop: $local_fs $network $remote_fs
# Should-Start: $NetworkManager
@ -12,55 +12,55 @@
# Description: starts instance of SickGear using start-stop-daemon
### END INIT INFO
# Source SickBeard configuration
if [ -f /etc/default/sickbeard ]; then
. /etc/default/sickbeard
# Source SickGear configuration
if [ -f /etc/default/sickgear ]; then
. /etc/default/sickgear
else
echo "/etc/default/sickbeard not found using default settings.";
echo "/etc/default/sickgear not found using default settings.";
fi
# Source init functions
. /lib/lsb/init-functions
# Script name
NAME=sickbeard
NAME=sickgear
# App name
DESC=SickBeard
DESC=SickGear
## Don't edit this file
## Edit user configuation in /etc/default/sickbeard to change
## Edit user configuation in /etc/default/sickgear to change
##
## SB_USER= #$RUN_AS, username to run sickbeard under, the default is sickbeard
## SB_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickbeard
## SB_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickbeard
## SB_PIDFILE= #$PID_FILE, the location of sickbeard.pid, the default is /var/run/sickbeard/sickbeard.pid
## SG_USER= #$RUN_AS, username to run sickgear under, the default is sickgear
## SG_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickgear
## SG_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickgear
## SG_PIDFILE= #$PID_FILE, the location of sickgear.pid, the default is /var/run/sickgear/sickgear.pid
## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python
## SB_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for sickbeard, i.e. " --config=/home/sickbeard/config.ini"
## SG_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for sickgear, i.e. " --config=/home/sickgear/config.ini"
## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users"
##
## EXAMPLE if want to run as different user
## add SB_USER=username to /etc/default/sickbeard
## otherwise default sickbeard is used
## add SG_USER=username to /etc/default/sickgear
## otherwise default sickgear is used
## The defaults
# Run as username
RUN_AS=${SB_USER-sickbeard}
# Run as username
RUN_AS=${SG_USER-sickgear}
# Path to app SB_HOME=path_to_app_SickBeard.py
APP_PATH=${SB_HOME-/opt/sickbeard}
# Path to app SG_HOME=path_to_app_SickBeard.py
APP_PATH=${SG_HOME-/opt/sickgear}
# Data directory where sickbeard.db, cache and logs are stored
DATA_DIR=${SB_DATA-/opt/sickbeard}
DATA_DIR=${SG_DATA-/opt/sickgear}
# Path to store PID file
PID_FILE=${SB_PIDFILE-/var/run/sickbeard/sickbeard.pid}
PID_FILE=${SG_PIDFILE-/var/run/sickgear/sickgear.pid}
# path to python bin
DAEMON=${PYTHON_BIN-/usr/bin/python}
# Extra daemon option like: SB_OPTS=" --config=/home/sickbeard/config.ini"
EXTRA_DAEMON_OPTS=${SB_OPTS-}
# Extra daemon option like: SG_OPTS=" --config=/home/sickgear/config.ini"
EXTRA_DAEMON_OPTS=${SG_OPTS-}
# Extra start-stop-daemon option like START_OPTS=" --group=users"
EXTRA_SSD_OPTS=${SSD_OPTS-}
@ -75,7 +75,7 @@ test -x $DAEMON || exit 0
set -e
# Create PID directory if not exist and ensure the SickBeard user can write to it
# Create PID directory if not exist and ensure the SickGear user can write to it
if [ ! -d $PID_PATH ]; then
mkdir -p $PID_PATH
chown $RUN_AS $PID_PATH
@ -94,28 +94,28 @@ if [ -e $PID_FILE ]; then
fi
fi
start_sickbeard() {
start_sickgear() {
echo "Starting $DESC"
start-stop-daemon -d $APP_PATH -c $RUN_AS $EXTRA_SSD_OPTS --start --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS
}
stop_sickbeard() {
stop_sickgear() {
echo "Stopping $DESC"
start-stop-daemon --stop --pidfile $PID_FILE --retry 15
}
case "$1" in
start)
start_sickbeard
start_sickgear
;;
stop)
stop_sickbeard
stop_sickgear
;;
restart|force-reload)
stop_sickbeard
stop_sickgear
sleep 2
start_sickbeard
start_sickgear
;;
status)
status_of_proc -p "$PID_FILE" "$DAEMON" "$DESC"

View file

@ -1,98 +0,0 @@
#!/bin/sh
#
# PROVIDE: sickbeard
# REQUIRE: LOGIN
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
# to enable this service:
#
# sickbeard_enable (bool): Set to NO by default.
# Set it to YES to enable it.
# sickbeard_user: The user account SickGear daemon runs as what
# you want it to be. It uses '_sabnzbd' user by
# default. Do not sets it as empty or it will run
# as root.
# sickbeard_dir: Directory where SickGear lives.
# Default: /usr/local/sickbeard
# sickbeard_chdir: Change to this directory before running SickGear.
# Default is same as sickbeard_dir.
# sickbeard_datadir: Data directory for Sick Beard (DB, Logs, config)
# Default is same as sickbeard_chdir
# sickbeard_pid: The name of the pidfile to create.
# Default is sickbeard.pid in sickbeard_dir.
# sickbeard_host: The hostname or IP SickGear is listening on
# Default is 127.0.0.1
# sickbeard_port: The port SickGear is listening on
# Default is 8081
# sickbeard_web_user: Username to authenticate to the SickGear web interface
# Default is an empty string (no username)
# sickbeard_web_password: Password to authenticate to the SickGear web interface
# Default is an empty string (no password)
# sickbeard_webroot: Set to value of web_root in config (for proxies etc)
# Default is an empty string (if set must start with a "/")
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
. /etc/rc.subr
name="sickbeard"
rcvar=${name}_enable
load_rc_config ${name}
: ${sickbeard_enable:="NO"}
: ${sickbeard_user:="_sabnzbd"}
: ${sickbeard_dir:="/usr/local/sickbeard"}
: ${sickbeard_chdir:="${sickbeard_dir}"}
: ${sickbeard_datadir:="${sickbeard_chdir}"}
: ${sickbeard_pid:="${sickbeard_dir}/sickbeard.pid"}
: ${sickbeard_host:="127.0.0.1"}
: ${sickbeard_port:="8081"}
: ${sickbeard_web_user:=""}
: ${sickbeard_web_password:=""}
: ${sickbeard_webroot:=""}
status_cmd="${name}_status"
stop_cmd="${name}_stop"
command="/usr/sbin/daemon"
command_args="-f -p ${sickbeard_pid} python ${sickbeard_dir}/SickBeard.py --quiet --nolaunch"
# Add datadir to the command if set
[ ! -z "${sickbeard_datadir}" ] && \
command_args="${command_args} --datadir ${sickbeard_datadir}"
# Ensure user is root when running this script.
if [ `id -u` != "0" ]; then
echo "Oops, you should be root before running this!"
exit 1
fi
verify_sickbeard_pid() {
# Make sure the pid corresponds to the SickGear process.
pid=`cat ${sickbeard_pid} 2>/dev/null`
ps -p ${pid} 2>/dev/null | grep -q "python ${sickbeard_dir}/SickBeard.py"
return $?
}
# Try to stop SickGear cleanly by calling shutdown over http.
sickbeard_stop() {
echo "Stopping $name"
verify_sickbeard_pid
sickbeard_url="${sickbeard_host}:${sickbeard_port}"
[ ! -z "${sickbeard_web_user}" ] && \
sickbeard_url="${sickbeard_web_user}:${sickbeard_web_password}@${sickbeard_url}"
[ ! -z "${sickbeard_webroot}" ] && \
sickbeard_url="${sickbeard_url}${sickbeard_webroot}"
fetch -o - -q "http://${sickbeard_url}/home/shutdown/?pid=${pid}" >/dev/null
if [ -n "${pid}" ]; then
wait_for_pids ${pid}
echo "Stopped"
fi
}
sickbeard_status() {
verify_sickbeard_pid && echo "$name is running as ${pid}" || echo "$name is not running"
}
run_rc_command "$1"

View file

@ -61,8 +61,9 @@ class Connection(threading.Thread):
self._iamALIVE = False
self.counter = 0
self.counterAge = 0
# start with a throttled connection
self.counter = 6
self.counterAge = time()
def print_log(self, data):
print(strftime("%Y-%m-%d %H:%M:%S", localtime(time())) + ": " + str(data))

View file

@ -17,8 +17,8 @@ http://www.crummy.com/software/BeautifulSoup/bs4/doc/
"""
__author__ = "Leonard Richardson (leonardr@segfault.org)"
__version__ = "4.3.2"
__copyright__ = "Copyright (c) 2004-2013 Leonard Richardson"
__version__ = "4.4.0"
__copyright__ = "Copyright (c) 2004-2015 Leonard Richardson"
__license__ = "MIT"
__all__ = ['BeautifulSoup']
@ -77,10 +77,11 @@ class BeautifulSoup(Tag):
ASCII_SPACES = '\x20\x0a\x09\x0c\x0d'
NO_PARSER_SPECIFIED_WARNING = "No parser was explicitly specified, so I'm using the best available parser for this system (\"%(parser)s\"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.\n\nTo get rid of this warning, change this:\n\n BeautifulSoup([your markup])\n\nto this:\n\n BeautifulSoup([your markup], \"%(parser)s\")\n"
NO_PARSER_SPECIFIED_WARNING = "No parser was explicitly specified, so I'm using the best available %(markup_type)s parser for this system (\"%(parser)s\"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.\n\nTo get rid of this warning, change this:\n\n BeautifulSoup([your markup])\n\nto this:\n\n BeautifulSoup([your markup], \"%(parser)s\")\n"
def __init__(self, markup="", features=None, builder=None,
parse_only=None, from_encoding=None, **kwargs):
parse_only=None, from_encoding=None, exclude_encodings=None,
**kwargs):
"""The Soup object is initialized as the 'root tag', and the
provided markup (which can be a string or a file-like object)
is fed into the underlying parser."""
@ -156,8 +157,13 @@ class BeautifulSoup(Tag):
builder = builder_class()
if not (original_features == builder.NAME or
original_features in builder.ALTERNATE_NAMES):
if builder.is_xml:
markup_type = "XML"
else:
markup_type = "HTML"
warnings.warn(self.NO_PARSER_SPECIFIED_WARNING % dict(
parser=builder.NAME))
parser=builder.NAME,
markup_type=markup_type))
self.builder = builder
self.is_xml = builder.is_xml
@ -202,7 +208,8 @@ class BeautifulSoup(Tag):
for (self.markup, self.original_encoding, self.declared_html_encoding,
self.contains_replacement_characters) in (
self.builder.prepare_markup(markup, from_encoding)):
self.builder.prepare_markup(
markup, from_encoding, exclude_encodings=exclude_encodings)):
self.reset()
try:
self._feed()
@ -215,6 +222,16 @@ class BeautifulSoup(Tag):
self.markup = None
self.builder.soup = None
def __copy__(self):
return type(self)(self.encode(), builder=self.builder)
def __getstate__(self):
# Frequently a tree builder can't be pickled.
d = dict(self.__dict__)
if 'builder' in d and not self.builder.picklable:
del d['builder']
return d
def _feed(self):
# Convert the document to Unicode.
self.builder.reset()
@ -241,9 +258,7 @@ class BeautifulSoup(Tag):
def new_string(self, s, subclass=NavigableString):
"""Create a new NavigableString associated with this soup."""
navigable = subclass(s)
navigable.setup()
return navigable
return subclass(s)
def insert_before(self, successor):
raise NotImplementedError("BeautifulSoup objects don't support insert_before().")
@ -302,14 +317,49 @@ class BeautifulSoup(Tag):
def object_was_parsed(self, o, parent=None, most_recent_element=None):
"""Add an object to the parse tree."""
parent = parent or self.currentTag
most_recent_element = most_recent_element or self._most_recent_element
o.setup(parent, most_recent_element)
previous_element = most_recent_element or self._most_recent_element
next_element = previous_sibling = next_sibling = None
if isinstance(o, Tag):
next_element = o.next_element
next_sibling = o.next_sibling
previous_sibling = o.previous_sibling
if not previous_element:
previous_element = o.previous_element
o.setup(parent, previous_element, next_element, previous_sibling, next_sibling)
if most_recent_element is not None:
most_recent_element.next_element = o
self._most_recent_element = o
parent.contents.append(o)
if parent.next_sibling:
# This node is being inserted into an element that has
# already been parsed. Deal with any dangling references.
index = parent.contents.index(o)
if index == 0:
previous_element = parent
previous_sibling = None
else:
previous_element = previous_sibling = parent.contents[index-1]
if index == len(parent.contents)-1:
next_element = parent.next_sibling
next_sibling = None
else:
next_element = next_sibling = parent.contents[index+1]
o.previous_element = previous_element
if previous_element:
previous_element.next_element = o
o.next_element = next_element
if next_element:
next_element.previous_element = o
o.next_sibling = next_sibling
if next_sibling:
next_sibling.previous_sibling = o
o.previous_sibling = previous_sibling
if previous_sibling:
previous_sibling.next_sibling = o
def _popToTag(self, name, nsprefix=None, inclusivePop=True):
"""Pops the tag stack up to and including the most recent
instance of the given tag. If inclusivePop is false, pops the tag

View file

@ -85,6 +85,7 @@ class TreeBuilder(object):
features = []
is_xml = False
picklable = False
preserve_whitespace_tags = set()
empty_element_tags = None # A tag will be considered an empty-element
# tag when and only when it has no contents.

View file

@ -2,6 +2,7 @@ __all__ = [
'HTML5TreeBuilder',
]
from pdb import set_trace
import warnings
from bs4.builder import (
PERMISSIVE,
@ -9,7 +10,10 @@ from bs4.builder import (
HTML_5,
HTMLTreeBuilder,
)
from bs4.element import NamespacedAttribute
from bs4.element import (
NamespacedAttribute,
whitespace_re,
)
import html5lib
from html5lib.constants import namespaces
from bs4.element import (
@ -26,9 +30,16 @@ class HTML5TreeBuilder(HTMLTreeBuilder):
features = [NAME, PERMISSIVE, HTML_5, HTML]
def prepare_markup(self, markup, user_specified_encoding):
def prepare_markup(self, markup, user_specified_encoding,
document_declared_encoding=None, exclude_encodings=None):
# Store the user-specified encoding for use later on.
self.user_specified_encoding = user_specified_encoding
# document_declared_encoding and exclude_encodings aren't used
# ATM because the html5lib TreeBuilder doesn't use
# UnicodeDammit.
if exclude_encodings:
warnings.warn("You provided a value for exclude_encoding, but the html5lib tree builder doesn't support exclude_encoding.")
yield (markup, None, None, False)
# These methods are defined by Beautiful Soup.
@ -103,7 +114,13 @@ class AttrList(object):
def __iter__(self):
return list(self.attrs.items()).__iter__()
def __setitem__(self, name, value):
"set attr", name, value
# If this attribute is a multi-valued attribute for this element,
# turn its value into a list.
list_attr = HTML5TreeBuilder.cdata_list_attributes
if (name in list_attr['*']
or (self.element.name in list_attr
and name in list_attr[self.element.name])):
value = whitespace_re.split(value)
self.element[name] = value
def items(self):
return list(self.attrs.items())
@ -180,6 +197,7 @@ class Element(html5lib.treebuilders._base.Node):
return AttrList(self.element)
def setAttributes(self, attributes):
if attributes is not None and len(attributes) > 0:
converted_attributes = []
@ -226,6 +244,9 @@ class Element(html5lib.treebuilders._base.Node):
def reparentChildren(self, new_parent):
"""Move all of this tag's children into another tag."""
# print "MOVE", self.element.contents
# print "FROM", self.element
# print "TO", new_parent.element
element = self.element
new_parent_element = new_parent.element
# Determine what this tag's next_element will be once all the children
@ -244,17 +265,28 @@ class Element(html5lib.treebuilders._base.Node):
new_parents_last_descendant_next_element = new_parent_element.next_element
to_append = element.contents
append_after = new_parent.element.contents
append_after = new_parent_element.contents
if len(to_append) > 0:
# Set the first child's previous_element and previous_sibling
# to elements within the new parent
first_child = to_append[0]
first_child.previous_element = new_parents_last_descendant
if new_parents_last_descendant:
first_child.previous_element = new_parents_last_descendant
else:
first_child.previous_element = new_parent_element
first_child.previous_sibling = new_parents_last_child
if new_parents_last_descendant:
new_parents_last_descendant.next_element = first_child
else:
new_parent_element.next_element = first_child
if new_parents_last_child:
new_parents_last_child.next_sibling = first_child
# Fix the last child's next_element and next_sibling
last_child = to_append[-1]
last_child.next_element = new_parents_last_descendant_next_element
if new_parents_last_descendant_next_element:
new_parents_last_descendant_next_element.previous_element = last_child
last_child.next_sibling = None
for child in to_append:
@ -265,6 +297,10 @@ class Element(html5lib.treebuilders._base.Node):
element.contents = []
element.next_element = final_next_element
# print "DONE WITH MOVE"
# print "FROM", self.element
# print "TO", new_parent_element
def cloneNode(self):
tag = self.soup.new_tag(self.element.name, self.namespace)
node = Element(tag, self.soup, self.namespace)

View file

@ -4,10 +4,16 @@ __all__ = [
'HTMLParserTreeBuilder',
]
from HTMLParser import (
HTMLParser,
HTMLParseError,
)
from HTMLParser import HTMLParser
try:
from HTMLParser import HTMLParseError
except ImportError, e:
# HTMLParseError is removed in Python 3.5. Since it can never be
# thrown in 3.5, we can just define our own class as a placeholder.
class HTMLParseError(Exception):
pass
import sys
import warnings
@ -20,8 +26,10 @@ import warnings
# strict=True works well on Python 3.2.2.
major, minor, release = sys.version_info[:3]
CONSTRUCTOR_TAKES_STRICT = major == 3 and minor == 2 and release >= 3
CONSTRUCTOR_STRICT_IS_DEPRECATED = major == 3 and minor == 3
CONSTRUCTOR_TAKES_CONVERT_CHARREFS = major == 3 and minor >= 4
from bs4.element import (
CData,
Comment,
@ -119,18 +127,19 @@ class BeautifulSoupHTMLParser(HTMLParser):
class HTMLParserTreeBuilder(HTMLTreeBuilder):
is_xml = False
picklable = True
NAME = HTMLPARSER
features = [NAME, HTML, STRICT]
def __init__(self, *args, **kwargs):
if CONSTRUCTOR_TAKES_STRICT:
if CONSTRUCTOR_TAKES_STRICT and not CONSTRUCTOR_STRICT_IS_DEPRECATED:
kwargs['strict'] = False
if CONSTRUCTOR_TAKES_CONVERT_CHARREFS:
kwargs['convert_charrefs'] = False
self.parser_args = (args, kwargs)
def prepare_markup(self, markup, user_specified_encoding=None,
document_declared_encoding=None):
document_declared_encoding=None, exclude_encodings=None):
"""
:return: A 4-tuple (markup, original encoding, encoding
declared within markup, whether any characters had to be
@ -141,7 +150,8 @@ class HTMLParserTreeBuilder(HTMLTreeBuilder):
return
try_encodings = [user_specified_encoding, document_declared_encoding]
dammit = UnicodeDammit(markup, try_encodings, is_html=True)
dammit = UnicodeDammit(markup, try_encodings, is_html=True,
exclude_encodings=exclude_encodings)
yield (dammit.markup, dammit.original_encoding,
dammit.declared_html_encoding,
dammit.contains_replacement_characters)

View file

@ -31,6 +31,7 @@ class LXMLTreeBuilderForXML(TreeBuilder):
is_xml = True
NAME = "lxml-xml"
ALTERNATE_NAMES = ["xml"]
# Well, it's permissive by XML parser standards.
features = [NAME, LXML, XML, FAST, PERMISSIVE]
@ -77,6 +78,7 @@ class LXMLTreeBuilderForXML(TreeBuilder):
return (None, tag)
def prepare_markup(self, markup, user_specified_encoding=None,
exclude_encodings=None,
document_declared_encoding=None):
"""
:yield: A series of 4-tuples.
@ -102,7 +104,8 @@ class LXMLTreeBuilderForXML(TreeBuilder):
# the document as each one in turn.
is_html = not self.is_xml
try_encodings = [user_specified_encoding, document_declared_encoding]
detector = EncodingDetector(markup, try_encodings, is_html)
detector = EncodingDetector(
markup, try_encodings, is_html, exclude_encodings)
for encoding in detector.encodings:
yield (detector.markup, encoding, document_declared_encoding, False)

View file

@ -3,10 +3,11 @@
This library converts a bytestream to Unicode through any means
necessary. It is heavily based on code from Mark Pilgrim's Universal
Feed Parser. It works best on XML and XML, but it does not rewrite the
Feed Parser. It works best on XML and HTML, but it does not rewrite the
XML or HTML to reflect a new encoding; that's the tree builder's job.
"""
from pdb import set_trace
import codecs
from htmlentitydefs import codepoint2name
import re
@ -212,8 +213,11 @@ class EncodingDetector:
5. Windows-1252.
"""
def __init__(self, markup, override_encodings=None, is_html=False):
def __init__(self, markup, override_encodings=None, is_html=False,
exclude_encodings=None):
self.override_encodings = override_encodings or []
exclude_encodings = exclude_encodings or []
self.exclude_encodings = set([x.lower() for x in exclude_encodings])
self.chardet_encoding = None
self.is_html = is_html
self.declared_encoding = None
@ -224,6 +228,8 @@ class EncodingDetector:
def _usable(self, encoding, tried):
if encoding is not None:
encoding = encoding.lower()
if encoding in self.exclude_encodings:
return False
if encoding not in tried:
tried.add(encoding)
return True
@ -266,6 +272,9 @@ class EncodingDetector:
def strip_byte_order_mark(cls, data):
"""If a byte-order mark is present, strip it and return the encoding it implies."""
encoding = None
if isinstance(data, unicode):
# Unicode data cannot have a byte-order mark.
return data, encoding
if (len(data) >= 4) and (data[:2] == b'\xfe\xff') \
and (data[2:4] != '\x00\x00'):
encoding = 'utf-16be'
@ -306,7 +315,7 @@ class EncodingDetector:
declared_encoding_match = html_meta_re.search(markup, endpos=html_endpos)
if declared_encoding_match is not None:
declared_encoding = declared_encoding_match.groups()[0].decode(
'ascii')
'ascii', 'replace')
if declared_encoding:
return declared_encoding.lower()
return None
@ -331,13 +340,14 @@ class UnicodeDammit:
]
def __init__(self, markup, override_encodings=[],
smart_quotes_to=None, is_html=False):
smart_quotes_to=None, is_html=False, exclude_encodings=[]):
self.smart_quotes_to = smart_quotes_to
self.tried_encodings = []
self.contains_replacement_characters = False
self.is_html = is_html
self.detector = EncodingDetector(markup, override_encodings, is_html)
self.detector = EncodingDetector(
markup, override_encodings, is_html, exclude_encodings)
# Short-circuit if the data is in Unicode to begin with.
if isinstance(markup, unicode) or markup == '':

View file

@ -33,12 +33,21 @@ def diagnose(data):
if 'lxml' in basic_parsers:
basic_parsers.append(["lxml", "xml"])
from lxml import etree
print "Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION))
try:
from lxml import etree
print "Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION))
except ImportError, e:
print (
"lxml is not installed or couldn't be imported.")
if 'html5lib' in basic_parsers:
import html5lib
print "Found html5lib version %s" % html5lib.__version__
try:
import html5lib
print "Found html5lib version %s" % html5lib.__version__
except ImportError, e:
print (
"html5lib is not installed or couldn't be imported.")
if hasattr(data, 'read'):
data = data.read()

View file

@ -1,3 +1,4 @@
from pdb import set_trace
import collections
import re
import sys
@ -185,24 +186,40 @@ class PageElement(object):
return self.HTML_FORMATTERS.get(
name, HTMLAwareEntitySubstitution.substitute_xml)
def setup(self, parent=None, previous_element=None):
def setup(self, parent=None, previous_element=None, next_element=None,
previous_sibling=None, next_sibling=None):
"""Sets up the initial relations between this element and
other elements."""
self.parent = parent
self.previous_element = previous_element
if previous_element is not None:
self.previous_element.next_element = self
self.next_element = None
self.previous_sibling = None
self.next_sibling = None
if self.parent is not None and self.parent.contents:
self.previous_sibling = self.parent.contents[-1]
self.next_element = next_element
if self.next_element:
self.next_element.previous_element = self
self.next_sibling = next_sibling
if self.next_sibling:
self.next_sibling.previous_sibling = self
if (not previous_sibling
and self.parent is not None and self.parent.contents):
previous_sibling = self.parent.contents[-1]
self.previous_sibling = previous_sibling
if previous_sibling:
self.previous_sibling.next_sibling = self
nextSibling = _alias("next_sibling") # BS3
previousSibling = _alias("previous_sibling") # BS3
def replace_with(self, replace_with):
if not self.parent:
raise ValueError(
"Cannot replace one element with another when the"
"element to be replaced is not part of a tree.")
if replace_with is self:
return
if replace_with is self.parent:
@ -216,6 +233,10 @@ class PageElement(object):
def unwrap(self):
my_parent = self.parent
if not self.parent:
raise ValueError(
"Cannot replace an element with its contents when that"
"element is not part of a tree.")
my_index = self.parent.index(self)
self.extract()
for child in reversed(self.contents[:]):
@ -240,17 +261,20 @@ class PageElement(object):
last_child = self._last_descendant()
next_element = last_child.next_element
if self.previous_element is not None:
if (self.previous_element is not None and
self.previous_element != next_element):
self.previous_element.next_element = next_element
if next_element is not None:
if next_element is not None and next_element != self.previous_element:
next_element.previous_element = self.previous_element
self.previous_element = None
last_child.next_element = None
self.parent = None
if self.previous_sibling is not None:
if (self.previous_sibling is not None
and self.previous_sibling != self.next_sibling):
self.previous_sibling.next_sibling = self.next_sibling
if self.next_sibling is not None:
if (self.next_sibling is not None
and self.next_sibling != self.previous_sibling):
self.next_sibling.previous_sibling = self.previous_sibling
self.previous_sibling = self.next_sibling = None
return self
@ -478,6 +502,10 @@ class PageElement(object):
def _find_all(self, name, attrs, text, limit, generator, **kwargs):
"Iterates over a generator looking for things that match."
if text is None and 'string' in kwargs:
text = kwargs['string']
del kwargs['string']
if isinstance(name, SoupStrainer):
strainer = name
else:
@ -558,7 +586,7 @@ class PageElement(object):
# | Attribute
# Tag
attribselect_re = re.compile(
r'^(?P<tag>[a-zA-Z0-9][-.a-zA-Z0-9:_]*)?\[(?P<attribute>\w+)(?P<operator>[=~\|\^\$\*]?)' +
r'^(?P<tag>[a-zA-Z0-9][-.a-zA-Z0-9:_]*)?\[(?P<attribute>[\w-]+)(?P<operator>[=~\|\^\$\*]?)' +
r'=?"?(?P<value>[^\]"]*)"?\]$'
)
@ -654,11 +682,17 @@ class NavigableString(unicode, PageElement):
how to handle non-ASCII characters.
"""
if isinstance(value, unicode):
return unicode.__new__(cls, value)
return unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)
u = unicode.__new__(cls, value)
else:
u = unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)
u.setup()
return u
def __copy__(self):
return self
"""A copy of a NavigableString has the same contents and class
as the original, but it is not connected to the parse tree.
"""
return type(self)(self)
def __getnewargs__(self):
return (unicode(self),)
@ -759,9 +793,12 @@ class Tag(PageElement):
self.prefix = prefix
if attrs is None:
attrs = {}
elif attrs and builder.cdata_list_attributes:
attrs = builder._replace_cdata_list_attribute_values(
self.name, attrs)
elif attrs:
if builder is not None and builder.cdata_list_attributes:
attrs = builder._replace_cdata_list_attribute_values(
self.name, attrs)
else:
attrs = dict(attrs)
else:
attrs = dict(attrs)
self.attrs = attrs
@ -778,6 +815,18 @@ class Tag(PageElement):
parserClass = _alias("parser_class") # BS3
def __copy__(self):
"""A copy of a Tag is a new Tag, unconnected to the parse tree.
Its contents are a copy of the old Tag's contents.
"""
clone = type(self)(None, self.builder, self.name, self.namespace,
self.nsprefix, self.attrs)
for attr in ('can_be_empty_element', 'hidden'):
setattr(clone, attr, getattr(self, attr))
for child in self.contents:
clone.append(child.__copy__())
return clone
@property
def is_empty_element(self):
"""Is this tag an empty-element tag? (aka a self-closing tag)
@ -971,15 +1020,25 @@ class Tag(PageElement):
as defined in __eq__."""
return not self == other
def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING):
def __repr__(self, encoding="unicode-escape"):
"""Renders this tag as a string."""
return self.encode(encoding)
if PY3K:
# "The return value must be a string object", i.e. Unicode
return self.decode()
else:
# "The return value must be a string object", i.e. a bytestring.
# By convention, the return value of __repr__ should also be
# an ASCII string.
return self.encode(encoding)
def __unicode__(self):
return self.decode()
def __str__(self):
return self.encode()
if PY3K:
return self.decode()
else:
return self.encode()
if PY3K:
__str__ = __repr__ = __unicode__
@ -1103,12 +1162,18 @@ class Tag(PageElement):
formatter="minimal"):
"""Renders the contents of this tag as a Unicode string.
:param indent_level: Each line of the rendering will be
indented this many spaces.
:param eventual_encoding: The tag is destined to be
encoded into this encoding. This method is _not_
responsible for performing that encoding. This information
is passed in so that it can be substituted in if the
document contains a <META> tag that mentions the document's
encoding.
:param formatter: The output formatter responsible for converting
entities to Unicode characters.
"""
# First off, turn a string formatter into a function. This
# will stop the lookup from happening over and over again.
@ -1137,7 +1202,17 @@ class Tag(PageElement):
def encode_contents(
self, indent_level=None, encoding=DEFAULT_OUTPUT_ENCODING,
formatter="minimal"):
"""Renders the contents of this tag as a bytestring."""
"""Renders the contents of this tag as a bytestring.
:param indent_level: Each line of the rendering will be
indented this many spaces.
:param eventual_encoding: The bytestring will be in this encoding.
:param formatter: The output formatter responsible for converting
entities to Unicode characters.
"""
contents = self.decode_contents(indent_level, encoding, formatter)
return contents.encode(encoding)
@ -1201,7 +1276,14 @@ class Tag(PageElement):
_selector_combinators = ['>', '+', '~']
_select_debug = False
def select(self, selector, _candidate_generator=None):
def select_one(self, selector):
"""Perform a CSS selection operation on the current element."""
value = self.select(selector, limit=1)
if value:
return value[0]
return None
def select(self, selector, _candidate_generator=None, limit=None):
"""Perform a CSS selection operation on the current element."""
# Remove whitespace directly after the grouping operator ','
@ -1272,35 +1354,38 @@ class Tag(PageElement):
"A pseudo-class must be prefixed with a tag name.")
pseudo_attributes = re.match('([a-zA-Z\d-]+)\(([a-zA-Z\d]+)\)', pseudo)
found = []
if pseudo_attributes is not None:
if pseudo_attributes is None:
pseudo_type = pseudo
pseudo_value = None
else:
pseudo_type, pseudo_value = pseudo_attributes.groups()
if pseudo_type == 'nth-of-type':
try:
pseudo_value = int(pseudo_value)
except:
raise NotImplementedError(
'Only numeric values are currently supported for the nth-of-type pseudo-class.')
if pseudo_value < 1:
raise ValueError(
'nth-of-type pseudo-class value must be at least 1.')
class Counter(object):
def __init__(self, destination):
self.count = 0
self.destination = destination
def nth_child_of_type(self, tag):
self.count += 1
if self.count == self.destination:
return True
if self.count > self.destination:
# Stop the generator that's sending us
# these things.
raise StopIteration()
return False
checker = Counter(pseudo_value).nth_child_of_type
else:
if pseudo_type == 'nth-of-type':
try:
pseudo_value = int(pseudo_value)
except:
raise NotImplementedError(
'Only the following pseudo-classes are implemented: nth-of-type.')
'Only numeric values are currently supported for the nth-of-type pseudo-class.')
if pseudo_value < 1:
raise ValueError(
'nth-of-type pseudo-class value must be at least 1.')
class Counter(object):
def __init__(self, destination):
self.count = 0
self.destination = destination
def nth_child_of_type(self, tag):
self.count += 1
if self.count == self.destination:
return True
if self.count > self.destination:
# Stop the generator that's sending us
# these things.
raise StopIteration()
return False
checker = Counter(pseudo_value).nth_child_of_type
else:
raise NotImplementedError(
'Only the following pseudo-classes are implemented: nth-of-type.')
elif token == '*':
# Star selector -- matches everything
@ -1376,6 +1461,7 @@ class Tag(PageElement):
else:
_use_candidate_generator = _candidate_generator
count = 0
for tag in current_context:
if self._select_debug:
print " Running candidate generator on %s %s" % (
@ -1400,6 +1486,8 @@ class Tag(PageElement):
# don't include it in the context more than once.
new_context.append(candidate)
new_context_ids.add(id(candidate))
if limit and len(new_context) >= limit:
break
elif self._select_debug:
print " FAILURE %s %s" % (candidate.name, repr(candidate.attrs))

View file

@ -28,7 +28,6 @@
import logging
import re
from io import BytesIO
from .enums import ProbingState
@ -79,16 +78,16 @@ class CharSetProber(object):
This filter applies to all scripts which do not use English characters.
"""
filtered = BytesIO()
filtered = bytearray()
# This regex expression filters out only words that have at-least one
# international character. The word may include one marker character at
# the end.
words = re.findall(
b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?', buf)
words = re.findall(b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?',
buf)
for word in words:
filtered.write(word[:-1])
filtered.extend(word[:-1])
# If the last character in the word is a marker, replace it with a
# space as markers shouldn't affect our analysis (they are used
@ -97,9 +96,9 @@ class CharSetProber(object):
last_char = word[-1:]
if not last_char.isalpha() and last_char < b'\x80':
last_char = b' '
filtered.write(last_char)
filtered.extend(last_char)
return filtered.getvalue()
return filtered
@staticmethod
def filter_with_english_letters(buf):
@ -113,7 +112,7 @@ class CharSetProber(object):
characters and extended ASCII characters, but is currently only used by
``Latin1Prober``.
"""
filtered = BytesIO()
filtered = bytearray()
in_tag = False
prev = 0
@ -132,15 +131,15 @@ class CharSetProber(object):
if curr > prev and not in_tag:
# Keep everything after last non-extended-ASCII,
# non-alphabetic character
filtered.write(buf[prev:curr])
filtered.extend(buf[prev:curr])
# Output a space to delimit stretch we kept
filtered.write(b' ')
filtered.extend(b' ')
prev = curr + 1
# If we're not in a tag...
if not in_tag:
# Keep everything after last non-extended-ASCII, non-alphabetic
# character
filtered.write(buf[prev:])
filtered.extend(buf[prev:])
return filtered.getvalue()
return filtered

View file

@ -1,16 +1,11 @@
"""
All of the Enums that are used throughout the chardet package.
:author: Dan Blanchard (dblanchard@ets.org)
:author: Dan Blanchard (dan.blanchard@gmail.com)
"""
try:
from enum import IntEnum
except ImportError:
from enum34 import IntEnum
class InputState(IntEnum):
class InputState(object):
"""
This enum represents the different states a universal detector can be in.
"""
@ -19,7 +14,7 @@ class InputState(IntEnum):
high_byte = 2
class LanguageFilter(IntEnum):
class LanguageFilter(object):
"""
This enum represents the different language filters we can apply to a
``UniversalDetector``.
@ -34,7 +29,7 @@ class LanguageFilter(IntEnum):
cjk = chinese | japanese | korean
class ProbingState(IntEnum):
class ProbingState(object):
"""
This enum represents the different states a prober can be in.
"""
@ -43,7 +38,7 @@ class ProbingState(IntEnum):
not_me = 2
class MachineState(IntEnum):
class MachineState(object):
"""
This enum represents the different states a state machine can be in.
"""

View file

@ -33,7 +33,7 @@ from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel,
Ibm866Model, Ibm855Model)
from .langgreekmodel import Latin7GreekModel, Win1253GreekModel
from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel
from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel
# from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel
from .langthaimodel import TIS620ThaiModel
from .langhebrewmodel import Win1255HebrewModel
from .hebrewprober import HebrewProber
@ -63,9 +63,9 @@ class SBCSGroupProber(CharSetGroupProber):
]
hebrew_prober = HebrewProber()
logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel,
False, hebrew_prober)
False, hebrew_prober)
visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True,
hebrew_prober)
hebrew_prober)
hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober)
self.probers.extend([hebrew_prober, logical_hebrew_prober,
visual_hebrew_prober])

View file

@ -122,12 +122,10 @@ class UniversalDetector(object):
if byte_str.startswith(codecs.BOM_UTF8):
# EF BB BF UTF-8 with BOM
self.result = {'encoding': "UTF-8-SIG", 'confidence': 1.0}
elif byte_str.startswith(codecs.BOM_UTF32_LE):
elif byte_str.startswith(codecs.BOM_UTF32_LE) or byte_str.startswith(codecs.BOM_UTF32_BE):
# FF FE 00 00 UTF-32, little-endian BOM
self.result = {'encoding': "UTF-32LE", 'confidence': 1.0}
elif byte_str.startswith(codecs.BOM_UTF32_BE):
# 00 00 FE FF UTF-32, big-endian BOM
self.result = {'encoding': "UTF-32BE", 'confidence': 1.0}
self.result = {'encoding': "UTF-32", 'confidence': 1.0}
elif byte_str.startswith(b'\xFE\xFF\x00\x00'):
# FE FF 00 00 UCS-4, unusual octet order BOM (3412)
self.result = {'encoding': "X-ISO-10646-UCS-4-3412",
@ -136,12 +134,10 @@ class UniversalDetector(object):
# 00 00 FF FE UCS-4, unusual octet order BOM (2143)
self.result = {'encoding': "X-ISO-10646-UCS-4-2143",
'confidence': 1.0}
elif byte_str.startswith(codecs.BOM_LE):
elif byte_str.startswith(codecs.BOM_LE) or byte_str.startswith(codecs.BOM_BE):
# FF FE UTF-16, little endian BOM
self.result = {'encoding': "UTF-16LE", 'confidence': 1.0}
elif byte_str.startswith(codecs.BOM_BE):
# FE FF UTF-16, big endian BOM
self.result = {'encoding': "UTF-16BE", 'confidence': 1.0}
self.result = {'encoding': "UTF-16", 'confidence': 1.0}
self._got_data = True
if self.result['encoding'] is not None:
@ -207,7 +203,7 @@ class UniversalDetector(object):
return
self.done = True
if self._input_state == InputState.pure_ascii:
if self._input_state in (InputState.pure_ascii, InputState.esc_ascii):
self.result = {'encoding': 'ascii', 'confidence': 1.0}
return self.result
@ -229,7 +225,7 @@ class UniversalDetector(object):
if self.logger.getEffectiveLevel() == logging.DEBUG:
self.logger.debug('no probers hit minimum threshhold')
for prober in self._charset_probers[0].mProbers:
for prober in self._charset_probers[0].probers:
if not prober:
continue
self.logger.debug('%s confidence = %s', prober.charset_name,

View file

@ -4,28 +4,29 @@ This module offers a generic date/time string parser which is able to parse
most known formats to represent a date and/or time.
This module attempts to be forgiving with regards to unlikely input formats,
returning a datetime object even for dates which are ambiguous. If an element of
a date/time stamp is omitted, the following rules are applied:
returning a datetime object even for dates which are ambiguous. If an element
of a date/time stamp is omitted, the following rules are applied:
- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour
on a 12-hour clock (`0 <= hour <= 12`) *must* be specified if AM or PM is
on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is
specified.
- If a time zone is omitted, it is assumed to be UTC.
- If a time zone is omitted, a timezone-naive datetime is returned.
If any other elements are missing, they are taken from the `datetime.datetime`
object passed to the parameter `default`. If this results in a day number
exceeding the valid number of days per month, one can fall back to the last
day of the month by setting `fallback_on_invalid_day` parameter to `True`.
If any other elements are missing, they are taken from the
:class:`datetime.datetime` object passed to the parameter ``default``. If this
results in a day number exceeding the valid number of days per month, one can
fall back to the last day of the month by setting ``fallback_on_invalid_day``
parameter to ``True``.
Also provided is the `smart_defaults` option, which attempts to fill in the
Also provided is the ``smart_defaults`` option, which attempts to fill in the
missing elements from context. If specified, the logic is:
- If the omitted element is smaller than the largest specified element, select
the *earliest* time matching the specified conditions; so `"June 2010"` is
interpreted as `June 1, 2010 0:00:00`) and the (somewhat strange)
`"Feb 1997 3:15 PM"` is interpreted as `February 1, 1997 15:15:00`.
the *earliest* time matching the specified conditions; so ``"June 2010"`` is
interpreted as ``June 1, 2010 0:00:00``) and the (somewhat strange)
``"Feb 1997 3:15 PM"`` is interpreted as ``February 1, 1997 15:15:00``.
- If the element is larger than the largest specified element, select the
*most recent* time matching the specified conditions (e.g parsing `"May"`
*most recent* time matching the specified conditions (e.g parsing ``"May"``
in June 2015 returns the date May 1st, 2015, whereas parsing it in April 2015
returns May 1st 2014). If using the `date_in_future` flag, this logic is
returns May 1st 2014). If using the ``date_in_future`` flag, this logic is
inverted, and instead the *next* time matching the specified conditions is
returned.
@ -46,6 +47,7 @@ import datetime
import string
import time
import collections
import re
from io import StringIO
from calendar import monthrange, isleap
@ -58,6 +60,9 @@ __all__ = ["parse", "parserinfo"]
class _timelex(object):
# Fractional seconds are sometimes split by a comma
_split_decimal = re.compile("([\.,])")
def __init__(self, instream):
if isinstance(instream, binary_type):
instream = instream.decode()
@ -80,8 +85,8 @@ class _timelex(object):
"""
This function breaks the time string into lexical units (tokens), which
can be parsed by the parser. Lexical units are demarcated by changes in
the character set, so any continuous string of letters is considered one
unit, any continuous string of numbers is considered one unit.
the character set, so any continuous string of letters is considered
one unit, any continuous string of numbers is considered one unit.
The main complication arises from the fact that dots ('.') can be used
both as separators (e.g. "Sep.20.2009") or decimal points (e.g.
@ -101,9 +106,9 @@ class _timelex(object):
whitespace = self.whitespace
while not self.eof:
# We only realize that we've reached the end of a token when we find
# a character that's not part of the current token - since that
# character may be part of the next token, it's stored in the
# We only realize that we've reached the end of a token when we
# find a character that's not part of the current token - since
# that character may be part of the next token, it's stored in the
# charstack.
if self.charstack:
nextchar = self.charstack.pop(0)
@ -145,7 +150,7 @@ class _timelex(object):
# numbers until we find something that doesn't fit.
if nextchar in numchars:
token += nextchar
elif nextchar == '.':
elif nextchar == '.' or (nextchar == ',' and len(token) >= 2):
token += nextchar
state = '0.'
else:
@ -176,14 +181,16 @@ class _timelex(object):
break # emit token
if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
token[-1] == '.')):
l = token.split('.')
token[-1] in '.,')):
l = self._split_decimal.split(token)
token = l[0]
for tok in l[1:]:
self.tokenstack.append('.')
if tok:
self.tokenstack.append(tok)
if state == '0.' and token.count('.') == 0:
token = token.replace(',', '.')
return token
def __iter__(self):
@ -224,20 +231,20 @@ class _resultbase(object):
class parserinfo(object):
"""
Class which handles what inputs are accepted. Subclass this to customize the
language and acceptable values for each parameter.
Class which handles what inputs are accepted. Subclass this to customize
the language and acceptable values for each parameter.
:param dayfirst:
Whether to interpret the first value in an ambiguous 3-integer date
(e.g. 01/05/09) as the day (`True`) or month (`False`). If
`yearfirst` is set to `True`, this distinguishes between YDM and
YMD. Default is `False`.
(e.g. 01/05/09) as the day (``True``) or month (``False``). If
``yearfirst`` is set to ``True``, this distinguishes between YDM
and YMD. Default is ``False``.
:param yearfirst:
Whether to interpret the first value in an ambiguous 3-integer date
(e.g. 01/05/09) as the year. If `True`, the first number is taken to
be the year, otherwise the last number is taken to be the year.
Default is `False`.
(e.g. 01/05/09) as the year. If ``True``, the first number is taken
to be the year, otherwise the last number is taken to be the year.
Default is ``False``.
"""
# m from a.m/p.m, t from ISO T separator
@ -287,7 +294,7 @@ class parserinfo(object):
self.smart_defaults = smart_defaults
self._year = time.localtime().tm_year
self._century = self._year // 100*100
self._century = self._year // 100 * 100
def _convert(self, lst):
dct = {}
@ -313,7 +320,7 @@ class parserinfo(object):
def month(self, name):
if len(name) >= 3:
try:
return self._months[name.lower()]+1
return self._months[name.lower()] + 1
except KeyError:
pass
return None
@ -345,7 +352,7 @@ class parserinfo(object):
def convertyear(self, year):
if year < 100:
year += self._century
if abs(year-self._year) >= 50:
if abs(year - self._year) >= 50:
if year < self._year:
year += 100
else:
@ -373,65 +380,87 @@ class parser(object):
smart_defaults=None, date_in_future=False,
fallback_on_invalid_day=None, **kwargs):
"""
Parse the date/time string into a datetime object.
Parse the date/time string into a :class:`datetime.datetime` object.
:param timestr:
Any date/time string using the supported formats.
:param default:
The default datetime object, if this is a datetime object and not
`None`, elements specified in `timestr` replace elements in the
default object, unless `smart_defaults` is set to `True`, in which
case to the extent necessary, timestamps are calculated relative to
this date.
``None``, elements specified in ``timestr`` replace elements in the
default object, unless ``smart_defaults`` is set to ``True``, in
which case to the extent necessary, timestamps are calculated
relative to this date.
:param smart_defaults:
If using smart defaults, the `default` parameter is treated as the
effective parsing date/time, and the context of the datetime string
is determined relative to `default`. If `None`, this parameter is
inherited from the :class:`parserinfo` object.
If using smart defaults, the ``default`` parameter is treated as
the effective parsing date/time, and the context of the datetime
string is determined relative to ``default``. If ``None``, this
parameter is inherited from the :class:`parserinfo` object.
:param date_in_future:
If `smart_defaults` is `True`, the parser assumes by default that
the timestamp refers to a date in the past, and will return the
beginning of the most recent timespan which matches the time string
(e.g. if `default` is March 3rd, 2013, "Feb" parses to
If ``smart_defaults`` is ``True``, the parser assumes by default
that the timestamp refers to a date in the past, and will return
the beginning of the most recent timespan which matches the time
string (e.g. if ``default`` is March 3rd, 2013, "Feb" parses to
"Feb 1, 2013" and "May 3" parses to May 3rd, 2012). Setting this
parameter to `True` inverts this assumption, and returns the
parameter to ``True`` inverts this assumption, and returns the
beginning of the *next* matching timespan.
:param fallback_on_invalid_day:
If specified `True`, an otherwise invalid date such as "Feb 30" or
"June 32" falls back to the last day of the month. If specified as
"False", the parser is strict about parsing otherwise valid dates
that would turn up as invalid because of the fallback rules (e.g.
"Feb 2010" run with a default of January 30, 2010 and `smartparser`
set to `False` would would throw an error, rather than falling
back to the end of February). If `None` or unspecified, the date
falls back to the most recent valid date only if the invalid date
is created as a result of an unspecified day in the time string.
If specified ``True``, an otherwise invalid date such as "Feb 30"
or "June 32" falls back to the last day of the month. If specified
as "False", the parser is strict about parsing otherwise valid
dates that would turn up as invalid because of the fallback rules
(e.g. "Feb 2010" run with a default of January 30, 2010 and
``smartparser`` set to ``False`` would would throw an error, rather
than falling back to the end of February). If ``None`` or
unspecified, the date falls back to the most recent valid date only
if the invalid date is created as a result of an unspecified day in
the time string.
:param ignoretz:
Whether or not to ignore the time zone.
If set ``True``, time zones in parsed strings are ignored and a
naive :class:`datetime.datetime` object is returned.
:param tzinfos:
A time zone, to be applied to the date, if `ignoretz` is `True`.
This can be either a subclass of `tzinfo`, a time zone string or an
integer offset.
Additional time zone names / aliases which may be present in the
string. This argument maps time zone names (and optionally offsets
from those time zones) to time zones. This parameter can be a
dictionary with timezone aliases mapping time zone names to time
zones or a function taking two parameters (``tzname`` and
``tzoffset``) and returning a time zone.
The timezones to which the names are mapped can be an integer
offset from UTC in minutes or a :class:`tzinfo` object.
.. doctest::
:options: +NORMALIZE_WHITESPACE
>>> from dateutil.parser import parse
>>> from dateutil.tz import gettz
>>> tzinfos = {"BRST": -10800, "CST": gettz("America/Chicago")}
>>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -10800))
>>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
datetime.datetime(2012, 1, 19, 17, 21,
tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
This parameter is ignored if ``ignoretz`` is set.
:param **kwargs:
Keyword arguments as passed to `_parse()`.
Keyword arguments as passed to ``_parse()``.
:return:
Returns a `datetime.datetime` object or, if the `fuzzy_with_tokens`
option is `True`, returns a tuple, the first element being a
`datetime.datetime` object, the second a tuple containing the
fuzzy tokens.
Returns a :class:`datetime.datetime` object or, if the
``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
first element being a :class:`datetime.datetime` object, the second
a tuple containing the fuzzy tokens.
:raises ValueError:
Raised for invalid or unknown string format, if the provided
`tzinfo` is not in a valid format, or if an invalid date would
be created.
:class:`tzinfo` is not in a valid format, or if an invalid date
would be created.
:raises OverFlowError:
Raised if the parsed date exceeds the largest valid C integer on
@ -444,14 +473,11 @@ class parser(object):
if default is None:
effective_dt = datetime.datetime.now()
default = datetime.datetime.now().replace(hour=0, minute=0,
second=0, microsecond=0)
second=0, microsecond=0)
else:
effective_dt = default
if kwargs.get('fuzzy_with_tokens', False):
res, skipped_tokens = self._parse(timestr, **kwargs)
else:
res = self._parse(timestr, **kwargs)
res, skipped_tokens = self._parse(timestr, **kwargs)
if res is None:
raise ValueError("Unknown string format")
@ -464,7 +490,7 @@ class parser(object):
repl[attr] = value
# Choose the correct fallback position if requested by the
# `smart_defaults` parameter.
# ``smart_defaults`` parameter.
if smart_defaults:
# Determine if it refers to this year, last year or next year
if res.year is None:
@ -472,7 +498,7 @@ class parser(object):
# Explicitly deal with leap year problems
if res.month == 2 and (res.day is not None and
res.day == 29):
ly_offset = 4 if date_in_future else -4
next_year = 4 * (default.year // 4)
@ -583,36 +609,42 @@ class parser(object):
fuzzy_with_tokens=False):
"""
Private method which performs the heavy lifting of parsing, called from
`parse()`, which passes on its `kwargs` to this function.
``parse()``, which passes on its ``kwargs`` to this function.
:param timestr:
The string to parse.
:param dayfirst:
Whether to interpret the first value in an ambiguous 3-integer date
(e.g. 01/05/09) as the day (`True`) or month (`False`). If
`yearfirst` is set to `True`, this distinguishes between YDM and
YMD. If set to `None`, this value is retrieved from the current
`parserinfo` object (which itself defaults to `False`).
(e.g. 01/05/09) as the day (``True``) or month (``False``). If
``yearfirst`` is set to ``True``, this distinguishes between YDM
and YMD. If set to ``None``, this value is retrieved from the
current :class:`parserinfo` object (which itself defaults to
``False``).
:param yearfirst:
Whether to interpret the first value in an ambiguous 3-integer date
(e.g. 01/05/09) as the year. If `True`, the first number is taken to
be the year, otherwise the last number is taken to be the year. If
this is set to `None`, the value is retrieved from the current
`parserinfo` object (which itself defaults to `False`).
(e.g. 01/05/09) as the year. If ``True``, the first number is taken
to be the year, otherwise the last number is taken to be the year.
If this is set to ``None``, the value is retrieved from the current
:class:`parserinfo` object (which itself defaults to ``False``).
:param fuzzy:
Whether to allow fuzzy parsing, allowing for string like "Today is
January 1, 2047 at 8:21:00AM".
:param fuzzy_with_tokens:
If `True`, `fuzzy` is automatically set to True, and the parser will
return a tuple where the first element is the parsed
`datetime.datetime` datetimestamp and the second element is a tuple
containing the portions of the string which were ignored, e.g.
"Today is January 1, 2047 at 8:21:00AM" should return
`(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))`
If ``True``, ``fuzzy`` is automatically set to True, and the parser
will return a tuple where the first element is the parsed
:class:`datetime.datetime` datetimestamp and the second element is
a tuple containing the portions of the string which were ignored:
.. doctest::
>>> from dateutil.parser import parse
>>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
"""
if fuzzy_with_tokens:
fuzzy = True
@ -796,7 +828,7 @@ class parser(object):
assert mstridx == -1
mstridx = len(ymd)-1
else:
return None
return None, None
i += 1
@ -840,7 +872,7 @@ class parser(object):
i += 1
elif not fuzzy:
return None
return None, None
else:
i += 1
continue
@ -969,7 +1001,7 @@ class parser(object):
# -[0]3
res.tzoffset = int(l[i][:2])*3600
else:
return None
return None, None
i += 1
res.tzoffset *= signal
@ -987,7 +1019,7 @@ class parser(object):
# Check jumps
if not (info.jump(l[i]) or fuzzy):
return None
return None, None
if last_skipped_token_i == i - 1:
# recombine the tokens
@ -1002,7 +1034,7 @@ class parser(object):
len_ymd = len(ymd)
if len_ymd > 3:
# More than three members!?
return None
return None, None
elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
# One member, or two members with a month string
if mstridx != -1:
@ -1066,72 +1098,113 @@ class parser(object):
res.month, res.day, res.year = ymd
except (IndexError, ValueError, AssertionError):
return None
return None, None
if not info.validate(res):
return None
return None, None
if fuzzy_with_tokens:
return res, tuple(skipped_tokens)
else:
return res
return res, None
DEFAULTPARSER = parser()
def parse(timestr, parserinfo=None, **kwargs):
"""
Parse a string in one of the supported formats, using the `parserinfo`
parameters.
Parse a string in one of the supported formats, using the
``parserinfo`` parameters.
:param timestr:
A string containing a date/time stamp.
:param parserinfo:
A :class:`parserinfo` object containing parameters for the parser.
If `None`, the default arguments to the `parserinfo` constructor are
used.
If ``None``, the default arguments to the :class:`parserinfo`
constructor are used.
The `**kwargs` parameter takes the following keyword arguments:
The ``**kwargs`` parameter takes the following keyword arguments:
:param default:
The default datetime object, if this is a datetime object and not
`None`, elements specified in `timestr` replace elements in the
``None``, elements specified in ``timestr`` replace elements in the
default object.
:param ignoretz:
Whether or not to ignore the time zone (boolean).
If set ``True``, time zones in parsed strings are ignored and a naive
:class:`datetime` object is returned.
:param tzinfos:
A time zone, to be applied to the date, if `ignoretz` is `True`.
This can be either a subclass of `tzinfo`, a time zone string or an
integer offset.
Additional time zone names / aliases which may be present in the
string. This argument maps time zone names (and optionally offsets
from those time zones) to time zones. This parameter can be a
dictionary with timezone aliases mapping time zone names to time
zones or a function taking two parameters (``tzname`` and
``tzoffset``) and returning a time zone.
The timezones to which the names are mapped can be an integer
offset from UTC in minutes or a :class:`tzinfo` object.
.. doctest::
:options: +NORMALIZE_WHITESPACE
>>> from dateutil.parser import parse
>>> from dateutil.tz import gettz
>>> tzinfos = {"BRST": -10800, "CST": gettz("America/Chicago")}
>>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -10800))
>>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
datetime.datetime(2012, 1, 19, 17, 21,
tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
This parameter is ignored if ``ignoretz`` is set.
:param dayfirst:
Whether to interpret the first value in an ambiguous 3-integer date
(e.g. 01/05/09) as the day (`True`) or month (`False`). If
`yearfirst` is set to `True`, this distinguishes between YDM and
YMD. If set to `None`, this value is retrieved from the current
:class:`parserinfo` object (which itself defaults to `False`).
(e.g. 01/05/09) as the day (``True``) or month (``False``). If
``yearfirst`` is set to ``True``, this distinguishes between YDM and
YMD. If set to ``None``, this value is retrieved from the current
:class:`parserinfo` object (which itself defaults to ``False``).
:param yearfirst:
Whether to interpret the first value in an ambiguous 3-integer date
(e.g. 01/05/09) as the year. If `True`, the first number is taken to
(e.g. 01/05/09) as the year. If ``True``, the first number is taken to
be the year, otherwise the last number is taken to be the year. If
this is set to `None`, the value is retrieved from the current
:class:`parserinfo` object (which itself defaults to `False`).
this is set to ``None``, the value is retrieved from the current
:class:`parserinfo` object (which itself defaults to ``False``).
:param fuzzy:
Whether to allow fuzzy parsing, allowing for string like "Today is
January 1, 2047 at 8:21:00AM".
:param fuzzy_with_tokens:
If `True`, `fuzzy` is automatically set to True, and the parser will
return a tuple where the first element is the parsed
`datetime.datetime` datetimestamp and the second element is a tuple
containing the portions of the string which were ignored, e.g.
"Today is January 1, 2047 at 8:21:00AM" should return
`(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))`
If ``True``, ``fuzzy`` is automatically set to True, and the parser
will return a tuple where the first element is the parsed
:class:`datetime.datetime` datetimestamp and the second element is
a tuple containing the portions of the string which were ignored:
.. doctest::
>>> from dateutil.parser import parse
>>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
:return:
Returns a :class:`datetime.datetime` object or, if the
``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
first element being a :class:`datetime.datetime` object, the second
a tuple containing the fuzzy tokens.
:raises ValueError:
Raised for invalid or unknown string format, if the provided
:class:`tzinfo` is not in a valid format, or if an invalid date
would be created.
:raises OverFlowError:
Raised if the parsed date exceeds the largest valid C integer on
your system.
"""
if parserinfo:
return parser(parserinfo).parse(timestr, **kwargs)

View file

@ -423,6 +423,7 @@ Here is the behavior of operations with relativedelta:
self.hours == other.hours and
self.minutes == other.minutes and
self.seconds == other.seconds and
self.microseconds == other.microseconds and
self.leapdays == other.leapdays and
self.year == other.year and
self.month == other.month and

View file

@ -104,12 +104,12 @@ class tzoffset(datetime.tzinfo):
class tzlocal(datetime.tzinfo):
_std_offset = datetime.timedelta(seconds=-time.timezone)
if time.daylight:
_dst_offset = datetime.timedelta(seconds=-time.altzone)
else:
_dst_offset = _std_offset
def __init__(self):
self._std_offset = datetime.timedelta(seconds=-time.timezone)
if time.daylight:
self._dst_offset = datetime.timedelta(seconds=-time.altzone)
else:
self._dst_offset = self._std_offset
def utcoffset(self, dt):
if self._isdst(dt):

View file

@ -4,6 +4,8 @@ import struct
from six.moves import winreg
from .tz import tzname_in_python2
__all__ = ["tzwin", "tzwinlocal"]
ONEWEEK = datetime.timedelta(7)
@ -42,6 +44,7 @@ class tzwinbase(datetime.tzinfo):
else:
return datetime.timedelta(0)
@tzname_in_python2
def tzname(self, dt):
if self._isdst(dt):
return self._dstname
@ -89,8 +92,8 @@ class tzwin(tzwinbase):
"%s\%s" % (TZKEYNAME, name)) as tzkey:
keydict = valuestodict(tzkey)
self._stdname = keydict["Std"].encode("iso-8859-1")
self._dstname = keydict["Dlt"].encode("iso-8859-1")
self._stdname = keydict["Std"]
self._dstname = keydict["Dlt"]
self._display = keydict["Display"]
@ -129,8 +132,8 @@ class tzwinlocal(tzwinbase):
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
keydict = valuestodict(tzlocalkey)
self._stdname = keydict["StandardName"].encode("iso-8859-1")
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
self._stdname = keydict["StandardName"]
self._dstname = keydict["DaylightName"]
try:
with winreg.OpenKey(

View file

@ -16,14 +16,14 @@ from dateutil.tz import tzfile
__all__ = ["gettz", "gettz_db_metadata", "rebuild"]
_ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
_METADATA_FN = 'METADATA'
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
METADATA_FN = 'METADATA'
# python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but
# it's close enough for python2.6
_tar_open = TarFile.open
tar_open = TarFile.open
if not hasattr(TarFile, '__exit__'):
def _tar_open(*args, **kwargs):
def tar_open(*args, **kwargs):
return closing(TarFile.open(*args, **kwargs))
@ -34,7 +34,7 @@ class tzfile(tzfile):
def getzoneinfofile_stream():
try:
return BytesIO(get_data(__name__, _ZONEFILENAME))
return BytesIO(get_data(__name__, ZONEFILENAME))
except IOError as e: # TODO switch to FileNotFoundError?
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
return None
@ -43,7 +43,7 @@ def getzoneinfofile_stream():
class ZoneInfoFile(object):
def __init__(self, zonefile_stream=None):
if zonefile_stream is not None:
with _tar_open(fileobj=zonefile_stream, mode='r') as tf:
with tar_open(fileobj=zonefile_stream, mode='r') as tf:
# dict comprehension does not work on python2.6
# TODO: get back to the nicer syntax when we ditch python2.6
# self.zones = {zf.name: tzfile(tf.extractfile(zf),
@ -52,7 +52,7 @@ class ZoneInfoFile(object):
self.zones = dict((zf.name, tzfile(tf.extractfile(zf),
filename=zf.name))
for zf in tf.getmembers()
if zf.isfile() and zf.name != _METADATA_FN)
if zf.isfile() and zf.name != METADATA_FN)
# deal with links: They'll point to their parent object. Less
# waste of memory
# links = {zl.name: self.zones[zl.linkname]
@ -62,7 +62,7 @@ class ZoneInfoFile(object):
zl.islnk() or zl.issym())
self.zones.update(links)
try:
metadata_json = tf.extractfile(tf.getmember(_METADATA_FN))
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
metadata_str = metadata_json.read().decode('UTF-8')
self.metadata = json.loads(metadata_str)
except KeyError:
@ -100,36 +100,3 @@ def gettz_db_metadata():
return _CLASS_ZONE_INSTANCE[0].metadata
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
filename is the timezone tarball from ftp.iana.org/tz.
"""
tmpdir = tempfile.mkdtemp()
zonedir = os.path.join(tmpdir, "zoneinfo")
moduledir = os.path.dirname(__file__)
try:
with _tar_open(filename) as tf:
for name in zonegroups:
tf.extract(name, tmpdir)
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
try:
check_call(["zic", "-d", zonedir] + filepaths)
except OSError as e:
if e.errno == 2:
logging.error(
"Could not find zic. Perhaps you need to install "
"libc-bin or some other package that provides it, "
"or it's not in your PATH?")
raise
# write metadata file
with open(os.path.join(zonedir, _METADATA_FN), 'w') as f:
json.dump(metadata, f, indent=4, sort_keys=True)
target = os.path.join(moduledir, _ZONEFILENAME)
with _tar_open(target, "w:%s" % format) as tf:
for entry in os.listdir(zonedir):
entrypath = os.path.join(zonedir, entry)
tf.add(entrypath, entry)
finally:
shutil.rmtree(tmpdir)

View file

@ -0,0 +1,43 @@
import logging
import os
import tempfile
import shutil
import json
from subprocess import check_call
from dateutil.zoneinfo import tar_open, METADATA_FN, ZONEFILENAME
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
filename is the timezone tarball from ftp.iana.org/tz.
"""
tmpdir = tempfile.mkdtemp()
zonedir = os.path.join(tmpdir, "zoneinfo")
moduledir = os.path.dirname(__file__)
try:
with tar_open(filename) as tf:
for name in zonegroups:
tf.extract(name, tmpdir)
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
try:
check_call(["zic", "-d", zonedir] + filepaths)
except OSError as e:
if e.errno == 2:
logging.error(
"Could not find zic. Perhaps you need to install "
"libc-bin or some other package that provides it, "
"or it's not in your PATH?")
raise
# write metadata file
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
json.dump(metadata, f, indent=4, sort_keys=True)
target = os.path.join(moduledir, ZONEFILENAME)
with tar_open(target, "w:%s" % format) as tf:
for entry in os.listdir(zonedir):
entrypath = os.path.join(zonedir, entry)
tf.add(entrypath, entry)
finally:
shutil.rmtree(tmpdir)

View file

@ -1,2 +1,2 @@
from lib.hachoir_core.version import VERSION as __version__, PACKAGE, WEBSITE, LICENSE
from hachoir_core.version import VERSION as __version__, PACKAGE, WEBSITE, LICENSE

View file

@ -1,5 +1,5 @@
from lib.hachoir_core.tools import humanDurationNanosec
from lib.hachoir_core.i18n import _
from hachoir_core.tools import humanDurationNanosec
from hachoir_core.i18n import _
from math import floor
from time import time

View file

@ -3,8 +3,8 @@ Utilities to convert integers and binary strings to binary (number), binary
string, number, hexadecimal, etc.
"""
from lib.hachoir_core.endian import BIG_ENDIAN, LITTLE_ENDIAN
from lib.hachoir_core.compatibility import reversed
from hachoir_core.endian import BIG_ENDIAN, LITTLE_ENDIAN, MIDDLE_ENDIAN
from hachoir_core.compatibility import reversed
from itertools import chain, repeat
from struct import calcsize, unpack, error as struct_error
@ -30,6 +30,28 @@ def swap32(value):
| ((value & 0x00FF0000L) >> 8) \
| ((value & 0xFF000000L) >> 24)
def arrswapmid(data):
r"""
Convert an array of characters from middle-endian to big-endian and vice-versa.
>>> arrswapmid("badcfehg")
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
"""
assert len(data)%2 == 0
ret = ['']*len(data)
ret[1::2] = data[0::2]
ret[0::2] = data[1::2]
return ret
def strswapmid(data):
r"""
Convert raw data from middle-endian to big-endian and vice-versa.
>>> strswapmid("badcfehg")
'abcdefgh'
"""
return ''.join(arrswapmid(data))
def bin2long(text, endian):
"""
Convert binary number written in a string into an integer.
@ -45,9 +67,10 @@ def bin2long(text, endian):
assert endian in (LITTLE_ENDIAN, BIG_ENDIAN)
bits = [ (ord(character)-ord("0")) \
for character in text if character in "01" ]
assert len(bits) != 0
if endian is not BIG_ENDIAN:
bits = reversed(bits)
bits = bits[::-1]
size = len(bits)
assert 0 < size
value = 0
for bit in bits:
value *= 2
@ -142,7 +165,7 @@ def long2raw(value, endian, size=None):
'\x19\x12\x00\x00'
"""
assert (not size and 0 < value) or (0 <= value)
assert endian in (LITTLE_ENDIAN, BIG_ENDIAN)
assert endian in (LITTLE_ENDIAN, BIG_ENDIAN, MIDDLE_ENDIAN)
text = []
while (value != 0 or text == ""):
byte = value % 256
@ -153,13 +176,15 @@ def long2raw(value, endian, size=None):
else:
need = 0
if need:
if endian is BIG_ENDIAN:
text = chain(repeat("\0", need), reversed(text))
else:
if endian is LITTLE_ENDIAN:
text = chain(text, repeat("\0", need))
else:
text = chain(repeat("\0", need), reversed(text))
else:
if endian is BIG_ENDIAN:
if endian is not LITTLE_ENDIAN:
text = reversed(text)
if endian is MIDDLE_ENDIAN:
text = arrswapmid(text)
return "".join(text)
def long2bin(size, value, endian, classic_mode=False):
@ -257,6 +282,8 @@ def str2long(data, endian):
True
>>> str2long("\xff\xff\xff\xff\xff\xff\xff\xff", BIG_ENDIAN) == (2**64-1)
True
>>> str2long("\x0b\x0a\x0d\x0c", MIDDLE_ENDIAN) == 0x0a0b0c0d
True
"""
assert 1 <= len(data) <= 32 # arbitrary limit: 256 bits
try:
@ -264,14 +291,15 @@ def str2long(data, endian):
except KeyError:
pass
assert endian in (BIG_ENDIAN, LITTLE_ENDIAN)
assert endian in (BIG_ENDIAN, LITTLE_ENDIAN, MIDDLE_ENDIAN)
shift = 0
value = 0
if endian is BIG_ENDIAN:
data = reversed(data)
elif endian is MIDDLE_ENDIAN:
data = reversed(strswapmid(data))
for character in data:
byte = ord(character)
value += (byte << shift)
shift += 8
return value

View file

@ -1,8 +1,8 @@
from optparse import OptionGroup
from lib.hachoir_core.log import log
from lib.hachoir_core.i18n import _, getTerminalCharset
from lib.hachoir_core.tools import makePrintable
import lib.hachoir_core.config as config
from hachoir_core.log import log
from hachoir_core.i18n import _, getTerminalCharset
from hachoir_core.tools import makePrintable
import hachoir_core.config as config
def getHachoirOptions(parser):
"""

View file

@ -14,7 +14,7 @@ unicode_stdout = True # Replace stdout and stderr with Unicode compatible ob
# Global options
debug = False # Display many informations usefull to debug
verbose = False # Display more informations
quiet = False # Don't display warnings
quiet = True # Don't display warnings
# Use internationalization and localization (gettext)?
if os.name == "nt":

View file

@ -2,8 +2,8 @@
Dictionnary classes which store values order.
"""
from lib.hachoir_core.error import HachoirError
from lib.hachoir_core.i18n import _
from hachoir_core.error import HachoirError
from hachoir_core.i18n import _
class UniqKeyError(HachoirError):
"""

Some files were not shown because too many files have changed in this diff Show more