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) ### 0.10.0 (2015-08-06 11:05:00 UTC)
* Remove EZRSS provider * 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 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 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) * 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) ### 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 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 * 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 * 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 * 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 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 * 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 * Add "Enable IMDb info" option to config/General/Interface
* Change to not display IMDb info on UI when "Enable IMDb info" is disabled * 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 * 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 anime release groups to in memory storage for lowered latency
* Change adjust menu delay and hover styling * Change adjust menu delay and hover styling
* Fix provider list color * 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 * Fix black and white list initialization on new show creation
* Add select all and clear all buttons to testRename template * Add select all and clear all buttons to testRename template
* Fix displayShow topmenu variable to point to a valid menu item * 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 * 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 SQL statements that have dynamic table names to use proper syntax
* Fix port checking code preventing startup directly after a SG restart * 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 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 * Change the default state for all check boxes on the Episode Overview page to not checked
* Add validation to Go button to ensure at least one item is checked on Episode Overview page * Add 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 * Add return code from hardlinking error to log
* Fix ABD regex for certain filenames * Fix ABD regex for certain filenames
* Change miscellaneous UI fixes * 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 * Fix trending shows page from loading full size poster images
* Add "Archive on first match" to Manage, Mass Update, Edit Selected page * Add "Archive on first match" to Manage, Mass Update, Edit Selected page
* Fix searching IPTorrentsProvider * Fix searching IPTorrentsProvider

View file

@ -1,7 +1,10 @@
Libs with customisations... 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/connectionpool.py
/lib/requests/packages/urllib3/util/ssl_.py /lib/requests/packages/urllib3/util/ssl_.py
/lib/cachecontrol/caches/file_cache.py /tornado
/lib/pynma/pynma.py /lib/unrar2/unix.py
/lib/tvdb/tvdb_api.py

View file

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

View file

@ -20,7 +20,7 @@ inc_top.tmpl
} }
.browserDialog.busy .ui-dialog-buttonpane{ .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{ .ui-progressbar .ui-progressbar-overlay{
@ -61,6 +61,8 @@ inc_top.tmpl
.ui-state-default, .ui-state-default,
.ui-widget-content .ui-state-default, .ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default{ .ui-widget-header .ui-state-default{
background:#3d3d3d;
color:#fff;
border:1px solid #111 border:1px solid #111
} }
@ -326,15 +328,15 @@ ul#rootDirStaticList li{
} }
/* ======================================================================= /* =======================================================================
home_trendingShows.tmpl home_browseShows.tmpl
========================================================================== */ ========================================================================== */
.traktContainer{ .browse-container{
background-color:#333; background-color:#333;
border:1px solid #111 border:1px solid #111
} }
.trakt-image{ .browse-image{
border-bottom:1px solid #111 border-bottom:1px solid #111
} }
@ -526,7 +528,16 @@ h2.day, h2.network{
.carousel-indicators .active{ .carousel-indicators .active{
background:#8DBEEE; background:#8DBEEE;
border-color:#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 config*.tmpl
@ -626,6 +637,14 @@ div.metadataDiv .disabled{
background:url("../images/warning16.png") no-repeat right 5px center #fff 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 manage*.tmpl
========================================================================== */ ========================================================================== */
@ -655,6 +674,7 @@ span.path{
background-color:#333 background-color:#333
} }
#addRootDirTable td label .filepath.red-text,
.red-text{ .red-text{
color:#d33 color:#d33
} }
@ -1159,12 +1179,14 @@ input sizing (for config pages)
========================================================================== */ ========================================================================== */
#pickShow optgroup, #pickShow optgroup,
#showfilter optgroup,
#editAProvider optgroup{ #editAProvider optgroup{
color:#eee; color:#eee;
background-color:rgb(51, 51, 51) background-color:rgb(51, 51, 51)
} }
#pickShow optgroup option, #pickShow optgroup option,
#showfilter optgroup option,
#editAProvider optgroup option{ #editAProvider optgroup option{
color:#222; color:#222;
background-color:#fff background-color:#fff
@ -1180,7 +1202,7 @@ browser.css
#fileBrowserDialog ul li a:hover{ #fileBrowserDialog ul li a:hover{
color:#09a2ff; color:#09a2ff;
background:none background: rgb(61, 61, 61) none
} }
.ui-menu .ui-menu-item{ .ui-menu .ui-menu-item{
@ -1214,6 +1236,10 @@ div.stepsguide .step p{
color:#646464 color:#646464
} }
#newShowPortal #addShowForm .stepsguide .disabledstep:hover > .smalltext{
color:#ccc;
}
div.stepsguide .disabledstep p{ div.stepsguide .disabledstep p{
border-color:#1178B3 border-color:#1178B3
} }
@ -1444,4 +1470,4 @@ jquery.confirm.css
#confirmBox .red:hover{ #confirmBox .red:hover{
background-color:#A13331 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{ .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{ .ui-progressbar .ui-progressbar-overlay{
background:url("../css/lib/images/animated-overlay.gif") background:url("../css/lib/images/animated-overlay.gif")
} }
.ui-dialog, .ui-dialog,
.ui-dialog-buttonpane{ .ui-dialog-buttonpane{
background:#eceadf url("../css/lib/images/ui-bg_fine-grain_10_eceadf_60x60.png") 50% 50% repeat !important 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{ .ui-tabs-panel{
background:#ededed !important; background:#ededed !important;
background-image:none !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 background:#fff url("../css/lib/images/ui-bg_flat_0_ffffff_40x100.png") 50% 50% repeat-x
} }
.ui-state-default, .ui-state-default,
.ui-widget-content .ui-state-default, .ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default{ .ui-widget-header .ui-state-default{
background:#fff; background:#fff;
border:1px solid #CCC border:1px solid #CCC
} }
.ui-state-hover, .ui-state-hover,
.ui-widget-content .ui-state-hover, .ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover, .ui-widget-header .ui-state-hover,
.ui-state-focus, .ui-state-focus,
.ui-widget-content .ui-state-focus, .ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus{ .ui-widget-header .ui-state-focus{
background:#fff background:#fff
} }
.ui-state-active, .ui-state-active,
.ui-widget-content .ui-state-active, .ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active{ .ui-widget-header .ui-state-active{
background:#F7F7F7 background:#F7F7F7
} }
.ui-state-highlight, .ui-state-highlight,
.ui-widget-content .ui-state-highlight, .ui-widget-content .ui-state-highlight,
.ui-widget-header .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 background:#fbf9ee url("../css/lib/images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x
} }
.ui-state-error, .ui-state-error,
.ui-widget-content .ui-state-error, .ui-widget-content .ui-state-error,
.ui-widget-header .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 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{ .ui-widget-content .ui-icon{
background-image:url("../css/lib/images/ui-icons_222222_256x240.png") 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") 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{ .ui-state-focus .ui-icon{
background-image:url("../css/lib/images/ui-icons_222222_256x240.png") 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") 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{ .ui-state-error-text .ui-icon{
background-image:url("../css/lib/images/ui-icons_cd0a0a_256x240.png") 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; background-color:#DFDACF;
border:1px solid #111 border:1px solid #111
} }
.trakt-image{ .browse-image{
border-bottom:1px solid #111 border-bottom:1px solid #111
} }
@ -345,7 +345,7 @@ home_postprocess.tmpl
/* ======================================================================= /* =======================================================================
displayShow.tmpl displayShow.tmpl
========================================================================== */ ========================================================================== */
tr.seasonheader{ tr.seasonheader{
@ -508,7 +508,16 @@ h2.day, h2.network{
.carousel-indicators .active{ .carousel-indicators .active{
background:#C7DB40; background:#C7DB40;
border-color:#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 config*.tmpl
@ -601,6 +610,14 @@ div.metadataDiv .disabled{
background:url("../images/warning16.png") no-repeat right 5px center #fff 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 manage*.tmpl
========================================================================== */ ========================================================================== */
@ -610,7 +627,7 @@ manage*.tmpl
} }
a.whitelink{ a.whitelink{
color:#fff color:#000
} }
/* ======================================================================= /* =======================================================================
@ -630,6 +647,7 @@ span.path{
background-color:#f5f1e4 background-color:#f5f1e4
} }
#addRootDirTable td label .filepath.red-text,
.red-text{ .red-text{
color:#d33 color:#d33
} }
@ -1125,12 +1143,14 @@ input sizing (for config pages)
========================================================================== */ ========================================================================== */
#pickShow optgroup, #pickShow optgroup,
#showfilter optgroup,
#editAProvider optgroup{ #editAProvider optgroup{
color:#eee; color:#eee;
background-color:#888 background-color:#888
} }
#pickShow optgroup option, #pickShow optgroup option,
#showfilter optgroup option,
#editAProvider optgroup option{ #editAProvider optgroup option{
color:#222; color:#222;
background-color:#fff background-color:#fff
@ -1142,7 +1162,7 @@ browser.css
#fileBrowserDialog ul li a:hover{ #fileBrowserDialog ul li a:hover{
color:#00f; color:#00f;
background:none background: rgb(220, 220, 220) none
} }
.ui-menu .ui-menu-item{ .ui-menu .ui-menu-item{
@ -1180,6 +1200,10 @@ div.stepsguide .disabledstep p{
border-color:#8a775e border-color:#8a775e
} }
#newShowPortal #addShowForm .stepsguide .disabledstep:hover > .smalltext{
color:#8a775e;
}
div.formpaginate .prev, div.formpaginate .next{ div.formpaginate .prev, div.formpaginate .next{
color:#fff; color:#fff;
background:#57442b background:#57442b
@ -1378,4 +1402,4 @@ jquery.confirm.css
#confirmBox .red:hover{ #confirmBox .red:hover{
background-color:#A13331 background-color:#A13331
} }

View file

@ -175,7 +175,7 @@ inc_top.tmpl
background:url("../css/lib/images/animated-overlay.gif") background:url("../css/lib/images/animated-overlay.gif")
} }
.ui-dialog, .ui-dialog,
.ui-dialog-buttonpane{ .ui-dialog-buttonpane{
background:#eceadf url("../css/lib/images/ui-bg_fine-grain_10_eceadf_60x60.png") 50% 50% repeat !important 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, #newShowPortal,
fieldset.sectionwrap, fieldset.sectionwrap,
div.formpaginate{ div.formpaginate{
width:801px width:831px
} }
#addShowForm{ #addShowForm{
@ -1054,25 +1054,25 @@ ul#rootDirStaticList li input[type="checkbox"]{
} }
/* ======================================================================= /* =======================================================================
home_trendingShows.tmpl home_browseShows.tmpl
========================================================================== */ ========================================================================== */
.traktShowTitleIcons{ .browse-add-show-holder{
float:right; float:right;
padding-right:4px; padding-right:4px;
padding-bottom:4px padding-bottom:4px
} }
.traktContainer p{ .browse-container p{
padding-top:2px padding-top:2px
} }
.traktContainer p img{ .browse-container p img{
position:relative; position:relative;
top:-2px top:-2px
} }
.traktContainer p, .traktContainer i{ .browse-container p, .browse-container i{
white-space:nowrap; white-space:nowrap;
font-size:12px; font-size:12px;
overflow:hidden; overflow:hidden;
@ -1080,15 +1080,16 @@ home_trendingShows.tmpl
margin:0 margin:0
} }
.traktContainer{ .browse-container{
margin:12px; margin:12px 12px 12px 0;
width:188px; width:188px;
background-color:#DFDACF; background-color:#DFDACF;
border:1px solid #111; border:1px solid #111;
border-radius:6px border-radius:6px
} }
.trakt-image{ .browse-image{
display:block;
overflow:hidden; overflow:hidden;
height:273px; height:273px;
width:186px; width:186px;
@ -1666,6 +1667,10 @@ td.col-search{
padding:15px 0 0 padding:15px 0 0
} }
#addShowForm #editShow.stepDiv span.component-desc{
width:639px
}
/* ======================================================================= /* =======================================================================
episodeView.tmpl episodeView.tmpl
========================================================================== */ ========================================================================== */
@ -2098,6 +2103,36 @@ td.col-cache{
width:20px 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 config*.tmpl
@ -2524,6 +2559,32 @@ div.metadataDiv .disabled{
margin:6px 4px 0 0 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 manage*.tmpl
========================================================================== */ ========================================================================== */
@ -2625,6 +2686,7 @@ span.path{
line-height:18px line-height:18px
} }
span.btn-text,
span.quality{ span.quality{
font:12px/13px "Open Sans", verdana, sans-serif; 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)); 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 display:inline
} }
#fileBrowserDialog .form-control{background-color:#f5f1e4}
#fileBrowserDialog .form-control:active,
#fileBrowserDialog .form-control:hover{background-color:#ffffca}
.btn{ .btn{
display:inline-block; display:inline-block;
*display:inline; *display:inline;
@ -3486,7 +3552,7 @@ div.stepsguide{
div.stepsguide .step{ div.stepsguide .step{
float:left; float:left;
width:267px; width:277px;
font:bold 24px Arial 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) 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 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"; sbRoot = "$sbRoot";
//--> //-->
</script> </script>
<script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.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?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/apibuilder.js?v=$sbPID"></script>
<style type="text/css"> <style type="text/css">
<!-- <!--

View file

@ -8,36 +8,23 @@
#set global $sbPath = '..' #set global $sbPath = '..'
#set global $topmenu = 'config' #set global $topmenu = 'config'
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
##
#if $varExists('header') #if $varExists('header')
<h1 class='header'>$header</h1> <h1 class='header'>$header</h1>
#else #else
<h1 class='title'>$title</h1> <h1 class='title'>$title</h1>
#end if #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"> <div id="config-content">
<table class="infoTable" cellspacing="1" border="0" cellpadding="0" width="100%"> <table class="infoTable" cellspacing="1" border="0" cellpadding="0" width="100%">
<tr> <tr>
<td class="infoTableHeader">Version: </td> <td class="infoTableHeader">Version: </td>
<td class="infoTableCell"> <td class="infoTableCell">
#if $sickbeard.VERSION_NOTIFY BRANCH: #echo $sickbeard.BRANCH or 'UNKNOWN'# / COMMIT: #echo $sickbeard.CUR_COMMIT_HASH or 'UNKNOWN'#<br />
BRANCH: ($sickbeard.BRANCH) / COMMIT: ($sickbeard.CUR_COMMIT_HASH) <!-- &ndash; build.date //--><br /> <em class="red-text">This is BETA software</em><br />
#else #if not $sickbeard.VERSION_NOTIFY:
You don't have version checking turned on, see "Check software updates" in Config > General.<br /> You don't have version checking turned on, see "Check software updates" in Config > General.
#end if #end if
<em class="red-text">This is BETA software.</em>
</td> </td>
</tr> </tr>
<tr><td class="infoTableHeader">Config file:</td><td class="infoTableCell">$sickbeard.CONFIG_FILE</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') #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') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
@ -36,16 +36,6 @@
</div> </div>
<fieldset class="component-group-list"> <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"> <div class="field-pair">
<label for="anime_treat_as_hdtv"> <label for="anime_treat_as_hdtv">
<span class="component-title">Quality control</span> <span class="component-title">Quality control</span>

View file

@ -32,8 +32,8 @@
#set $indexer = $sickbeard.INDEXER_DEFAULT #set $indexer = $sickbeard.INDEXER_DEFAULT
#end if #end if
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/config.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/rootDirs.js?v=$sbPID"></script>
<div id="config"> <div id="config">
<div id="config-content"> <div id="config-content">
@ -72,7 +72,7 @@
<span class="component-title">Update shows on startup</span> <span class="component-title">Update shows on startup</span>
<span class="component-desc"> <span class="component-desc">
<input type="checkbox" name="update_shows_on_start" id="update_shows_on_start"#echo ('', $checked)[$sickbeard.UPDATE_SHOWS_ON_START]#> <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> </span>
</label> </label>
</div> </div>
@ -86,7 +86,19 @@
</span> </span>
</label> </label>
</div> </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"> <div class="field-pair">
<span class="component-title">Send to trash for actions</span> <span class="component-title">Send to trash for actions</span>
<span class="component-desc"> <span class="component-desc">
@ -110,22 +122,22 @@
</span> </span>
</label> </label>
</div> </div>
#if 1 < $len($indexers)
<div class="field-pair"> <div class="field-pair">
<label for="indexer_default"> <label for="indexer_default">
<span class="component-title">Use initial indexer set to</span> <span class="component-title">Use initial indexer set to</span>
<span class="component-desc"> <span class="component-desc">
<select id="indexer_default" name="indexer_default" class="form-control input-sm"> <select id="indexer_default" name="indexer_default" class="form-control input-sm">
<option value="0"#echo ('', $selected)[0 == $indexer]#>All Indexers</option> <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> <option value="$indexer"#echo ('', $selected)[$indexer == $sickbeard.INDEXER_DEFAULT]#>$sickbeard.indexerApi().indexers[$indexer]</option>
#end for #end for
</select> </select>
<span>as the default selection when adding new shows</span> <span>as the default selection when adding new shows</span>
</span> </span>
</label> </label>
</div> </div>
#end if
<div class="field-pair"> <div class="field-pair">
<label for="indexer_timeout"> <label for="indexer_timeout">
<span class="component-title">Timeout show indexer at</span> <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-title">Use proxy for indexers</span>
<span class="component-desc"> <span class="component-desc">
<input type="checkbox" name="proxy_indexers" id="proxy_indexers"#echo ('', $checked)[True == $sickbeard.PROXY_INDEXERS]#> <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> </span>
</label> </label>
</div> </div>

View file

@ -1,5 +1,7 @@
#import base64
#import sickbeard #import sickbeard
#import re #import re
#from lib.libtrakt import TraktAPI
#from sickbeard.helpers import anon_url, starify #from sickbeard.helpers import anon_url, starify
## ##
#set global $title = 'Config - Notifications' #set global $title = 'Config - Notifications'
@ -9,12 +11,12 @@
## ##
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/configNotifications.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/configNotifications.js?v=$sbPID"></script>
<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') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
#else #else
<h1 class="title">$title</h1> <h1 class="title">$title</h1>
#end if #end if
@ -154,7 +156,7 @@
</div><!-- /xbmc component-group //--> </div><!-- /xbmc component-group //-->
<div class="component-group"> <div class="component-group">
<div class="component-group-desc"> <div class="component-group-desc">
<img class="notifier-icon" src="$sbRoot/images/notifiers/kodi.png" alt="" title="Kodi" /> <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> <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 //--> </div><!-- /content_use_kodi //-->
</fieldset> </fieldset>
</div><!-- /kodi component-group //--> </div><!-- /kodi component-group //-->
<div class="component-group"> <div class="component-group">
<div class="component-group-desc"> <div class="component-group-desc">
<img class="notifier-icon" src="$sbRoot/images/notifiers/plex.png" alt="" title="Plex Media Server" /> <img class="notifier-icon" src="$sbRoot/images/notifiers/plex.png" alt="" title="Plex Media Server" />
@ -1465,7 +1467,7 @@
<div class="component-group-desc"> <div class="component-group-desc">
<img class="notifier-icon" src="$sbRoot/images/notifiers/trakt.png" alt="" title="Trakt"/> <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> <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> </div>
<fieldset class="component-group-list"> <fieldset class="component-group-list">
<div class="field-pair"> <div class="field-pair">
@ -1473,42 +1475,85 @@
<span class="component-title">Enable</span> <span class="component-title">Enable</span>
<span class="component-desc"> <span class="component-desc">
<input type="checkbox" class="enabler" name="use_trakt" id="use_trakt" #if $sickbeard.USE_TRAKT then 'checked="checked"' else ''# /> <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> </span>
</label> </label>
</div> </div>
<div id="content_use_trakt"> <div id="content_use_trakt">
<div class="field-pair"> <div class="field-pair">
<label for="trakt_username"> <label for="trakt_accounts">
<span class="component-title">Trakt username</span> <span class="component-title">Trakt account (status):</span>
<input type="text" name="trakt_username" id="trakt_username" value="$sickbeard.TRAKT_USERNAME" class="form-control input-sm input250" /> <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>
<label> <label for="trakt_pin">
<span class="component-title">&nbsp;</span> <span class="component-title">Trakt PIN:</span>
<span class="component-desc">username of your Trakt account.</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> </label>
<div class="testNotification" id="trakt-authentication-result"></div>
</div> </div>
<div class="field-pair"> <div class="field-pair">
<label for="trakt_password"> <span class="trakt component-desc" style="width:100%">
<span class="component-title">Trakt password</span> #set num_accounts = len($trakt_accounts)
<input type="password" name="trakt_password" id="trakt_password" value="#echo '*' * len($sickbeard.TRAKT_PASSWORD)#" class="form-control input-sm input250" /> #set $num_columns = (1, num_accounts)[1 < num_accounts]
</label> <table id="trakt-collection" class="solid-border" cellpadding="0" cellspacing="0" border="0">
<label> <thead>
<span class="component-title">&nbsp;</span> <tr>
<span class="component-desc">password of your Trakt account.</span> <th class="col-1" style="font-size:12px;font-weight:normal" rowspan="2"><i>Update multiple accounts with downloaded episode info</i></th>
</label> <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>
</div> </tr>
<div class="field-pair"> <tr>
<label for="trakt_api"> #if not len($trakt_accounts)
<span class="component-title">Trakt API key:</span> <th>..</th>
<input type="text" name="trakt_api" id="trakt_api" value="<%= starify(sickbeard.TRAKT_API) %>" class="form-control input-sm input250" /> #end if
</label> #for $void, $account in $trakt_accounts.items()
<label> <th class="tid-$account.account_id">$account.name#if $account.active then '' else '<br />(inactive)'#</th>
<span class="component-title">&nbsp;</span> #end for
<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> </tr>
</label> </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>
<!--
<div class="field-pair"> <div class="field-pair">
<label for="trakt_default_indexer"> <label for="trakt_default_indexer">
<span class="component-title">Default indexer:</span> <span class="component-title">Default indexer:</span>
@ -1582,8 +1627,7 @@
</label> </label>
</div> </div>
</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" /> <input type="submit" class="btn config_submitter" value="Save Changes" />
</div><!-- /content_use_trakt //--> </div><!-- /content_use_trakt //-->
</fieldset> </fieldset>
@ -1743,4 +1787,4 @@
//--> //-->
</script> </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 #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/configPostProcessing.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/configPostProcessing.js?v=$sbPID"></script>
<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') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
#else #else
<h1 class="title">$title</h1> <h1 class="title">$title</h1>
#end if #end if
@ -1073,6 +1073,11 @@
<td>%RT</td> <td>%RT</td>
<td>PROPER</td> <td>PROPER</td>
</tr> </tr>
<tr class="even">
<td class="align-right"><b>Version:</b></td>
<td>%V</td>
<td>2</td>
</tr>
</tbody> </tbody>
</table> </table>
<br /> <br />

View file

@ -11,14 +11,14 @@
#import os.path #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
#if $varExists('header') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
#else #else
<h1 class="title">$title</h1> <h1 class="title">$title</h1>
#end if #end if
<script type="text/javascript" src="$sbRoot/js/configProviders.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/configProviders.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/config.js?v=$sbPID"></script>
#set $methods_notused = [] #set $methods_notused = []
#if not $sickbeard.USE_NZBS #if not $sickbeard.USE_NZBS
@ -281,7 +281,7 @@
<span class="component-desc"> <span class="component-desc">
#set $field_name = curNzbProvider.get_id() + '_api_key' #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" /> <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> <div class="clear-left"><p>${curNzbProvider.ui_string($field_name)}</p></div>
#end if #end if
</span> </span>
@ -347,6 +347,17 @@
## ##
#for $curTorrentProvider in [$curProvider for $curProvider in $sickbeard.providers.sortedProviderList() if $curProvider.providerType == $GenericProvider.TORRENT]: #for $curTorrentProvider in [$curProvider for $curProvider in $sickbeard.providers.sortedProviderList() if $curProvider.providerType == $GenericProvider.TORRENT]:
<div class="providerDiv" id="${curTorrentProvider.get_id()}Div"> <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'): #if $hasattr($curTorrentProvider, 'api_key'):
<div class="field-pair"> <div class="field-pair">
<label for="${curTorrentProvider.get_id()}_api_key"> <label for="${curTorrentProvider.get_id()}_api_key">
@ -360,9 +371,13 @@
#if $hasattr($curTorrentProvider, 'digest'): #if $hasattr($curTorrentProvider, 'digest'):
<div class="field-pair"> <div class="field-pair">
<label for="${curTorrentProvider.get_id()}_digest"> <label for="${curTorrentProvider.get_id()}_digest">
<span class="component-title">Digest:</span> <span class="component-title">Cookies:</span>
<span class="component-desc"> <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> </span>
</label> </label>
</div> </div>
@ -392,7 +407,7 @@
<label for="${curTorrentProvider.get_id()}_password"> <label for="${curTorrentProvider.get_id()}_password">
<span class="component-title">Password:</span> <span class="component-title">Password:</span>
<span class="component-desc"> <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> </span>
</label> </label>
</div> </div>
@ -408,14 +423,28 @@
</div> </div>
#end if #end if
#if $hasattr($curTorrentProvider, '_seed_ratio') and 'blackhole' != $sickbeard.TORRENT_METHOD: #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"> <div class="field-pair">
<label for="${curTorrentProvider.get_id()}_ratio"> <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-title" id="${curTorrentProvider.get_id()}_ratio_desc">Seed until ratio (the goal)</span>
<span class="component-desc"> <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" /> <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 download sent to $torrent_method_text[$sickbeard.TORRENT_METHOD]</p> <p>this ratio is requested of each item 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> <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> </span>
</label> </label>
</div> </div>
@ -489,6 +518,17 @@
</label> </label>
</div> </div>
#end if #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: #if $hasattr($curTorrentProvider, 'enable_recentsearch') and $curTorrentProvider.supportsBacklog:
<div class="field-pair"> <div class="field-pair">
<label for="${curTorrentProvider.get_id()}_enable_recentsearch"> <label for="${curTorrentProvider.get_id()}_enable_recentsearch">
@ -707,4 +747,4 @@
//--> //-->
</script> </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 #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/configSearch.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/configSearch.js?v=$sbPID"></script>
<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') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
@ -113,18 +113,42 @@
<label> <label>
<span class="component-title">Ignore result with any word</span> <span class="component-title">Ignore result with any word</span>
<span class="component-desc"> <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> <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>
<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> </label>
</div> </div>
<div class="field-pair"> <div class="field-pair">
<label> <label>
<span class="component-title">Require at least one word</span> <span class="component-title">Require all these words</span>
<span class="component-desc"> <span class="component-desc">
<input type="text" name="require_words" value="$sickbeard.REQUIRE_WORDS" class="form-control input-sm input350"> <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 one</em> of these comma seperated words</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> </span>
</label> </label>
</div> </div>
@ -473,9 +497,9 @@
<div class="field-pair" id="torrent_seed_time_option"> <div class="field-pair" id="torrent_seed_time_option">
<label> <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"> <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> </label>
</div> </div>

View file

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

View file

@ -15,19 +15,19 @@
#import os.path, os #import os.path, os
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<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"> <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/displayShow.js?v=$sbPID"></script>
<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" src="$sbRoot/js/sceneExceptionsTooltip.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/sceneExceptionsTooltip.js?v=$sbPID"></script>
#if $sickbeard.USE_IMDB_INFO #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 #end if
<script type="text/javascript" src="$sbRoot/js/ajaxEpSearch.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/ajaxEpSearch.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/ajaxEpSubtitles.js?$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?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/lib/jquery.collapser.min.js?v=$sbPID"></script>
<script type="text/javascript" charset="utf-8"> <script type="text/javascript" charset="utf-8">
<!-- <!--
\$(document).ready(function(){ \$(document).ready(function(){
@ -116,7 +116,7 @@
#end if #end if
<div id="details-wrapper"> <div id="details-wrapper">
<div id="details-right"> <div id="details-right">
#if $seasonResults #if 0 < len($seasonResults)
##There is a special/season_0?## ##There is a special/season_0?##
#set $season_special = (0, 1)[0 == int($seasonResults[-1]['season'])] #set $season_special = (0, 1)[0 == int($seasonResults[-1]['season'])]
## ##
@ -397,7 +397,11 @@
#end if #end if
## ##
#if 0 == len($sqlResults) #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: #else:
#for $epResult in $sqlResults #for $epResult in $sqlResults
#set $epStr = '%sx%s' % ($epResult['season'], $epResult['episode']) #set $epStr = '%sx%s' % ($epResult['season'], $epResult['episode'])
@ -518,7 +522,7 @@
<td class="col-name"> <td class="col-name">
<img src="$sbRoot/images/info32.png" width="16" height="16" alt="" class="plotInfo#echo '%s" />' %\ <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']]# ('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>
<td class="col-airdate"> <td class="col-airdate">

View file

@ -13,44 +13,14 @@
#import os.path #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/qualityChooser.js?v=$sbPID"></script>
<script type="text/javascript" charset="utf-8"> <script type="text/javascript" src="$sbRoot/js/editShow.js?v=$sbPID"></script>
<!-- <script>
\$(document).ready(function(){ var config = {
show_lang: "$show.lang",
\$.getJSON('$sbRoot/home/addShows/getIndexerLanguages', {}, function(data) { show_isanime: #echo ['!1','!0'][$show.is_anime]#
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> </script>
#if $varExists('header') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
#else #else
@ -99,15 +69,26 @@
<span class="component-title">Scene exception</span> <span class="component-title">Scene exception</span>
<span class="component-desc"> <span class="component-desc">
<input type="text" id="SceneName" class="form-control form-control-inline input-sm input200"> <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"> <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>
<span class="component-desc"> <span class="component-desc">
<div id="SceneException"> <div id="SceneException">
<h4 class="grey-text">Exceptions list (multi-selectable)</h4> <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" > <select id="exceptions_list" name="exceptions_list" multiple="multiple" class="input350" style="min-height:90px; float:left" >
#for $cur_exception in $show.exceptions: #for $cur_exception_season in $show.exceptions:
<option value="$cur_exception">$cur_exception</option> #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 #end for
</select> </select>
<span><p class="note">this list overrides the original name<br />to search, it doesn't append to it</p></span> <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-title">Ignore result with any word</span>
<span class="component-desc"> <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"> <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>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</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> </span>
</label> </label>
</div> </div>
@ -136,8 +117,8 @@
<span class="component-title">Require at least one word</span> <span class="component-title">Require at least one word</span>
<span class="component-desc"> <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"> <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>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</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> </span>
</label> </label>
</div> </div>
@ -186,7 +167,7 @@
<span class="component-title">Scene numbering</span> <span class="component-title">Scene numbering</span>
<span class="component-desc"> <span class="component-desc">
<input type="checkbox" name="scene" id="scene"#if $show.scene == 1 then $html_checked else ''#> <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> </span>
</label> </label>
</div> </div>
@ -248,73 +229,10 @@
#if $show.is_anime: #if $show.is_anime:
#import sickbeard.blackandwhitelist #import sickbeard.blackandwhitelist
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_blackwhitelist.tmpl') #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 #end if
<input type="submit" id="submit" value="Submit" class="btn btn-primary" /> <input type="submit" id="submit" value="Submit" class="btn btn-primary" />
</form> </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> </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 #end if
#if 'daybyday' != $layout: #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 #end if
#if $varExists('header') #if $varExists('header')
@ -30,7 +30,7 @@
#end if #end if
#if 'daybyday' == $layout: #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"> <script type="text/javascript" charset="utf-8">
<!-- <!--
\$(document).ready(function(){ \$(document).ready(function(){
@ -201,7 +201,7 @@
#if 'list' == $layout: #if 'list' == $layout:
<!-- start list view //--> <!-- 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"> <script type="text/javascript" charset="utf-8">
<!-- <!--
\$.tablesorter.addParser({ \$.tablesorter.addParser({
@ -643,6 +643,7 @@
#end if #end if
#else #else
#set $cur_result['state'] = $state_soon #set $cur_result['state'] = $state_soon
#set $cur_result['state-title'] = ''
$shows_soon.append($cur_result) $shows_soon.append($cur_result)
#end if #end if
#end for #end for

View file

@ -8,158 +8,26 @@
#set global $sbPath = '..' #set global $sbPath = '..'
#set global $topmenu = 'home' #set global $topmenu = 'home'
#set global $page_body_attr = 'show-list' #set global $page_body_attr = 'show-list'
#set fuzzydate = 'airdate'
## ##
#import os.path #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script>
<script type="text/javascript" charset="utf-8"> var config = {
<!-- isPoster: #echo ['!1','!0']['poster' == $sickbeard.HOME_LAYOUT]#,
sortArticle: #echo ['!1','!0'][$sickbeard.SORT_ARTICLE]#,
\$.tablesorter.addParser({ homeSearchFocus: #echo ['!1','!0'][$sickbeard.HOME_SEARCH_FOCUS]#,
id: 'loadingNames', fuzzyDating: #echo ['!1','!0'][$sickbeard.FUZZY_DATING]#,
is: function(s) { timeZero: #echo ['!1','!0'][$sickbeard.TRIM_ZERO]#,
return false; datePreset: "$sickbeard.DATE_PRESET",
}, timePreset: "$sickbeard.TIME_PRESET",
format: function(s) { posterSortby: "$sickbeard.POSTER_SORTBY",
if (s.indexOf('Loading...') == 0) posterSortdir: #echo ['!1','!0'][$sickbeard.POSTER_SORTDIR]#,
return s.replace('Loading...', '000'); fuzzydate: ".$fuzzydate",
else };
#if not $sickbeard.SORT_ARTICLE </script>
return (s || '').replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1'); <script type="text/javascript" src="$sbRoot/js/home.js?v=$sbPID"></script>
#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')
<h1 class="header" style="margin-bottom:0">$showlists[0][1]</h1> <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 #set $tab = 1
#if 'poster' != $layout #if 'poster' != $layout
@ -324,7 +192,7 @@
<div class="show-date"> <div class="show-date">
#if $cur_airs_next #if $cur_airs_next
#set $ldatetime = $sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($cur_airs_next,$curShow.airs,$curShow.network)) #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 #else
#set $output_html = '?' #set $output_html = '?'
#if None is not $display_status #if None is not $display_status
@ -471,7 +339,7 @@
<tr> <tr>
#if $cur_airs_next #if $cur_airs_next
#set $ldatetime = $sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($cur_airs_next,$curShow.airs,$curShow.network)) #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 #else
<td></td> <td></td>
#end if #end if
@ -550,21 +418,6 @@
#end if #end if
#end for #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') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -10,14 +10,11 @@
#import os.path #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<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"> <script type="text/javascript" charset="utf-8">
<!-- <!--
\$(document).ready(function(){ \$.sgSid = '$kwargs.get('sid', '')';
\$.sgHashDir = '$kwargs.get('hash_dir', '')';
\$(document).ready(function(){
\$( '#tabs' ).tabs({ \$( '#tabs' ).tabs({
collapsible: true, collapsible: true,
selected: #if $sickbeard.ROOT_DIRS then '-1' else '0'# selected: #if $sickbeard.ROOT_DIRS then '-1' else '0'#
@ -25,6 +22,10 @@
}); });
//--> //-->
</script> </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') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
@ -36,48 +37,56 @@
<form id="addShowForm" method="post" action="$sbRoot/home/addShows/addNewShow" accept-charset="utf-8"> <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"> <p style="margin-top:15px">
<input type="checkbox" id="promptForSettings" name="promptForSettings" style="vertical-align: top;" /> <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> <label for="promptForSettings">Enable to change the following options per show, otherwise use these options with all shows added below</label>
</p> </p>
<div id="tabs"> <div id="tabs">
<ul> <ul>
<li><a href="#tabs-1">Manage parent folders</a></li> <li><a href="#tabs-1">Manage parent folders</a></li>
<li><a href="#tabs-2">Custom options</a></li> <li><a href="#tabs-2">Custom options</a></li>
</ul> </ul>
<div id="tabs-1" class="existingtabs"> <div id="tabs-1" class="existingtabs">
<div style="width: 430px; margin: 0px auto"> <div style="width: 430px; margin: 0px auto">
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_rootDirs.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_rootDirs.tmpl')
</div>
</div> </div>
</div> <div id="tabs-2">
<div id="tabs-2"> <div class="stepDiv">
<div class="stepDiv">
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_addShowOptions.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_addShowOptions.tmpl')
</div>
</div> </div>
</div> </div>
</div>
<br /> <br />
<hr /> <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"> <ul id="rootDirStaticList">
<li></li> <li></li>
</ul> </ul>
#if not $kwargs.get('hash_dir', None)
<p>shows <span class="boldest">not known</span> to SickGear are listed below...</p> <p>shows <span class="boldest">not known</span> to SickGear are listed below...</p>
#end if
<div id="tableDiv"></div> <div id="tableDiv"></div>
<br /> <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> <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> </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 #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
#if $varExists('header') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
#else #else
<h1 class="title">$title</h1> <h1 class="title">$title</h1>
#end if #end if
<div id="addShowPortal"> <div id="addShowPortal">
<a class="btn btn-large" href="$sbRoot/home/addShows/newShow/"> <a class="btn btn-large" href="$sbRoot/home/addShows/newShow/">
<div class="button"><div class="icon-addnewshow"></div></div> <div class="button"><div class="icon-addnewshow"></div></div>
<div class="buttontext"> <div class="buttontext">
<h3>Add New Show</h3> <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> </div>
</a> </a>
<a class="btn btn-large" href="$sbRoot/home/addShows/trendingShows/"> <a class="btn btn-large" href="$sbRoot/home/addShows/trakt_default/">
<div class="button"><div class="icon-addtrendingshow"></div></div> <div class="button"><div class="icon-addrecommendedshow"></div></div>
<div class="buttontext"> <div class="buttontext">
<h3>Add From Trending</h3> <h3>Add From Trakt</h3>
<p>Browse a current trending show list to add from. A folder for episodes will be created</p> <p>Browse trends, recommended and more.</p>
</div> </div>
</a> </a>
<div style="clear:both;font-size:2px">&nbsp;</div>
<a class="btn btn-large" href="$sbRoot/home/addShows/existingShows/"> <a class="btn btn-large" href="$sbRoot/home/addShows/existingShows/">
<div class="button"><div class="icon-addexistingshow"></div></div> <div class="button"><div class="icon-addexistingshow"></div></div>
<div class="buttontext"> <div class="buttontext">
<h3>Add Existing Shows</h3> <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> </div>
</a> </a>
#if True == $sickbeard.USE_TRAKT: <a class="btn btn-large" href="$sbRoot/home/addShows/popular_imdb/">
<a class="btn btn-large" href="$sbRoot/home/addShows/recommendedShows/"> <div class="button"><div class="icon-addtrendingshow"></div></div>
<div class="button"><div class="icon-addrecommendedshow"></div></div>
<div class="buttontext"> <div class="buttontext">
<h3>Add Recommended</h3> <h3>Add From IMDb</h3>
<p>Browse recommendations based on your Trakt.tv show library to add to SickGear</p> <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> </div>
</a> </a>
#else #else
<div class="buttontext" style="padding:10px 5px"> <div class="buttontext" style="padding:10px 5px 10px 30px">
<p>There's more... unlock another button to browse<br /> <h3>Add Random/Hot AniDB</h3>
recommended shows based on your Trakt.tv show<br /> <p>To use, enable AniDB in Config/Anime.</p>
library by enabling Trakt in Config/Notifications/Social</p>
</div> </div>
#end if #end if
</div> </div>
<div style="clear:both">&nbsp;</div>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -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 #from sickbeard.helpers import anon_url
<table id="addRootDirTable" class="sickbeardTable tablesorter"> <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> <thead>
<tfoot> <tr>
<tr> <th class="col-checkbox">
<th rowspan="1" colspan="4" align="left"><a href="#" class="showManage">Manage Directories</a></th> <input type="checkbox" id="checkAll" checked=checked>
</tr> </th>
</tfoot> <th>Parent\show folder</th>
<tbody> <th width="20%">Show name<br />(tvshow.nfo)</th>
#for $curDir in $dirList: <th width="15%">TV info source</th>
#if $curDir['added_already']: </tr>
#continue </thead>
#end if <tfoot>
<tr>
#set $show_id = $curDir['dir'] <th rowspan="1" colspan="4" align="left">
#if $curDir['existing_info'][0]: <a href="#" class="showManage">Manage Directories</a>
#set $show_id = $show_id + '|' + $str($curDir['existing_info'][0]) + '|' + $str($curDir['existing_info'][1]) </th>
#set $indexer = $curDir['existing_info'][2] </tr>
#end if </tfoot>
<tbody>
#set $indexer = 0 #for $curDir in $dirList
#if $curDir['existing_info'][0]: #if $curDir['added_already'] and (None is $curDir.get('highlight') or not $kwargs.get('hash_dir'))
#set $indexer = $curDir['existing_info'][2] #continue
#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>
#end if #end if
<td align="center">
<select name="indexer"> #set $show_id = $curDir['dir']
#for $curIndexer in $sickbeard.indexerApi().indexers.items(): #if $curDir['existing_info'][0]
<option value="$curIndexer[0]" #if $curIndexer[0] == $indexer then "selected=\"selected\"" else "UNKNOWN"#>$curIndexer[1]</option> #set $show_id = $show_id + '|' + $str($curDir['existing_info'][0]) + '|' + $str($curDir['existing_info'][1])
#end for #set $indexer = $curDir['existing_info'][2]
</select> #end if
</td>
</tr> #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 #end for
</tbody> </tbody>
</tbody> </table>
</table>

View file

@ -10,10 +10,14 @@
#import os.path #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/formwizard.js?$sbPID"></script> <script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script> var show_scene_maps = ${show_scene_maps}
<script type="text/javascript" src="$sbRoot/js/newShow.js?$sbPID"></script> </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/newShow.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addShowOptions.js?v=$sbPID"></script>
#if $varExists('header') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
@ -31,7 +35,7 @@
<form id="addShowForm" method="post" action="$sbRoot/home/addShows/addNewShow" accept-charset="utf-8"> <form id="addShowForm" method="post" action="$sbRoot/home/addShows/addNewShow" accept-charset="utf-8">
<fieldset class="sectionwrap step-one"> <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"> <div class="stepDiv">
<input type="hidden" id="indexer_timeout" value="$sickbeard.INDEXER_TIMEOUT" /> <input type="hidden" id="indexer_timeout" value="$sickbeard.INDEXER_TIMEOUT" />
@ -39,29 +43,38 @@
#if $use_provided_info #if $use_provided_info
#set $provided_indexer_local = $provided_indexer #set $provided_indexer_local = $provided_indexer
#set $provided_indexer_id_local = $provided_indexer_id #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="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="providedName" value="$provided_indexer_name" />
<input type="hidden" id="providedIndexer" value="$provided_indexer" /> <input type="hidden" id="providedIndexer" value="$provided_indexer" />
#else #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" /> <input type="text" id="nameToSearch" value="$default_show_name" class="form-control form-control-inline input-sm input350" />
&nbsp; &nbsp;
<span style="float:right"> <span style="float:right">
<select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm"> <select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm">
<option value="en" selected="selected">en</option> <option value="en" selected="selected">en</option>
</select><b>&nbsp;*</b> </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> #if 1 < $len($indexers)
#for $indexer in $indexers <select name="providedIndexer" id="providedIndexer" class="form-control form-control-inline input-sm">
<option value="$indexer" #if $provided_indexer == $indexer then "selected=\"selected\"" else ""#>$indexers[$indexer]</option> <option value="0" #if $provided_indexer == 0 then "selected=\"selected\"" else ""#>All Indexers</option>
#end for #for $indexer in $indexers
</select> <option value="$indexer" #if $provided_indexer == $indexer then "selected=\"selected\"" else ""#>$indexers[$indexer]</option>
&nbsp; #end for
<input class="btn btn-inline" type="button" id="searchName" value="Search" /> </select>
#end if
&nbsp;
<input class="btn btn-inline" type="button" id="searchName" value="Search" />
</span> </span>
<br /> <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> <div id="searchResults" style="height: 100%"></div>
#end if #end if
@ -107,9 +120,9 @@
#end if #end if
</div> </div>
<script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/rootDirs.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/blackwhite.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/blackwhite.js?v=$sbPID"></script>
</div></div> </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 #import os.path
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl") #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
<script type="text/javascript" src="$sbRoot/js/formwizard.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/formwizard.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/qualityChooser.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/recommendedShows.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/recommendedShows.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/addShowOptions.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/addShowOptions.js?v=$sbPID"></script>
#if $varExists('header') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
@ -65,7 +65,7 @@
<input class="btn" type="button" id="addShowButton" value="Add Show" disabled="disabled" /> <input class="btn" type="button" id="addShowButton" value="Add Show" disabled="disabled" />
</div> </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> </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-title">Scene numbering</span>
<span class="component-desc"> <span class="component-desc">
<input type="checkbox" name="scene" id="scene" #if $sickbeard.SCENE_DEFAULT then "checked=\"checked\"" else ""# /> <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> </span>
</label> </label>
</div> </div>
@ -101,7 +101,7 @@
<label for="anime"> <label for="anime">
<span class="component-title">Show is anime</span> <span class="component-title">Show is anime</span>
<span class="component-desc"> <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> <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> </span>
</label> </label>

View file

@ -7,7 +7,7 @@
<span class="component-title input">Quality</span> <span class="component-title input">Quality</span>
<span class="component-desc"> <span class="component-desc">
#set $selected = None #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> <option value="0">Custom</option>
#for $curPreset in sorted($qualityPresets): #for $curPreset in sorted($qualityPresets):
<option value="$curPreset" #if $curPreset == $overall_quality then "selected=\"selected\"" else ""# #if $qualityPresetStrings[$curPreset].endswith("0p") then "style=\"padding-left: 15px;\"" else ""#>$qualityPresetStrings[$curPreset]</option> <option value="$curPreset" #if $curPreset == $overall_quality then "selected=\"selected\"" else ""# #if $qualityPresetStrings[$curPreset].endswith("0p") then "style=\"padding-left: 15px;\"" else ""#>$qualityPresetStrings[$curPreset]</option>

View file

@ -5,11 +5,12 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <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 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]> <!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> <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> <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="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="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="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-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-160x160.png" sizes="160x160">
<link rel="icon" type="image/png" href="$sbRoot/images/ico/favicon-96x96.png" sizes="96x96"> <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-TileImage" content="$sbRoot/images/ico/mstile-144x144.png">
<meta name="msapplication-config" content="$sbRoot/css/browserconfig.xml"> <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/lib/bootstrap.css?v=$sbPID"/>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/browser.css?$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?$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?$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?$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?$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?$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/jquery-1.8.3.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/lib/bootstrap.min.js?$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?$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?$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?$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?$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?$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?$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?$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?$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?$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"></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?$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?$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?$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?$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?$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?$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 #if $sickbeard.FUZZY_DATING
<script type="text/javascript" src="$sbRoot/js/moment/moment.min.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?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/fuzzyMoment.js?v=$sbPID"></script>
#end if #end if
<script type="text/javascript" charset="utf-8"> <script type="text/javascript" charset="utf-8">
<!-- <!--
var sbRoot = '$sbRoot', anonURL = '$sickbeard.ANON_REDIRECT', themeSpinner = '#echo ('', '-dark')['dark' == $sickbeard.THEME_NAME]#', 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>
<script type="text/javascript" src="$sbRoot/js/lib/jquery.scrolltopcontrol-1.1.js"></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/browser.js"></script>
<script type="text/javascript" src="$sbRoot/js/ajaxNotifications.js"></script> <script type="text/javascript" src="$sbRoot/js/ajaxNotifications.js"></script>
<script type="text/javascript"> <script type="text/javascript" src="$sbRoot/js/confirmations.js?v=$sbPID"></script>
<!--
#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>
</head> </head>
#set $tab = 4 #set $tab = 4
#set $body_attr = '' #set $body_attr = ''
@ -178,11 +134,6 @@
#if $sickbeard.USE_KODI and $sickbeard.KODI_HOST != '' #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> <li><a href="$sbRoot/home/updateKODI/" tabindex="$tab#set $tab += 1#"><i class="sgicon-kodi"></i>Update Kodi</a></li>
#end if #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 #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> <li><a href="$sbRoot/manage/failedDownloads/" tabindex="$tab#set $tab += 1#"><i class="sgicon-failed"></i>Failed Downloads</a></li>
#end if #end if
@ -217,9 +168,11 @@
<li class="dropdown"> <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> <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"> <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 #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 #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/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> <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 #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 #if $sickbeard.NEWEST_VERSION_STRING
<div class="alert alert-success upgrade-notification" role="alert"> <div class="alert alert-success upgrade-notification" role="alert">
<span>$sickbeard.NEWEST_VERSION_STRING</span> <span>$sickbeard.NEWEST_VERSION_STRING</span>

View file

@ -84,7 +84,7 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
}); });
//--> //-->
</script> </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') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
#else #else

View file

@ -10,9 +10,9 @@
#import os.path #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
#if $varExists('header') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
#else #else
<h1 class="title">$title</h1> <h1 class="title">$title</h1>
#end if #end if
## ##
@ -28,7 +28,7 @@
<select name="whichStatus" class="form-control form-control-inline input-sm" style="margin:0 10px"> <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]: #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 #end for
</select> </select>
@ -36,8 +36,10 @@
</form> </form>
## ##
#else #else
#if $whichStatus in ($common.ARCHIVED, $common.IGNORED, $common.SNATCHED): #if $whichStatus in ($common.ARCHIVED, $common.IGNORED):
#set $row_class = 'good' #set $row_class = 'good'
#elif $whichStatus == $common.SNATCHED:
#set $row_class = 'snatched'
#else #else
#set $row_class = $common.Overview.overviewStrings[$whichStatus] #set $row_class = $common.Overview.overviewStrings[$whichStatus]
#end if #end if
@ -51,7 +53,7 @@
$statusList.append($common.FAILED) $statusList.append($common.FAILED)
#end if #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"> <form action="$sbRoot/manage/changeEpisodeStatuses" method="post">
<input type="hidden" id="oldStatus" name="oldStatus" value="$whichStatus"> <input type="hidden" id="oldStatus" name="oldStatus" value="$whichStatus">
@ -108,4 +110,4 @@
</form> </form>
#end if #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>
<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') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>

View file

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

View file

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

View file

@ -9,7 +9,7 @@
#import os.path #import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<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"> <div id="content800">
#if $varExists('header') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>

View file

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

@ -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>
<script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.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?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/restart.js?v=$sbPID"></script>
#set themeSpinner = '-dark' if 'dark' == themeSpinner else '' #set themeSpinner = '-dark' if 'dark' == themeSpinner else ''
<h2>Performing Restart</h2> <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' window.location.href = sbRoot + '/home/addShows/addExistingShows'
+ '?promptForSettings=' + ($('#promptForSettings').prop('checked') ? 'on' : 'off') + '?promptForSettings=' + ($('#promptForSettings').prop('checked') ? 'on' : 'off')
+ (undefined !== $.sgSid && 0 < $.sgSid.length ? '&sid=' + $.sgSid : '')
+ '&shows_to_add=' + dirArr.join('&shows_to_add='); + '&shows_to_add=' + dirArr.join('&shows_to_add=');
}); });
@ -47,7 +48,7 @@ $(document).ready(function(){
+ ' height="32" width="32" />' + ' height="32" width="32" />'
+ ' scanning parent folders...'); + ' scanning parent folders...');
$.get(sbRoot + '/home/addShows/massAddTable', $.get(sbRoot + '/home/addShows/massAddTable' + (undefined !== $.sgHashDir && 0 < $.sgHashDir.length ? '?hash_dir=' + $.sgHashDir : ''),
url, url,
function(data){ function(data){
$('#tableDiv').html(data); $('#tableDiv').html(data);
@ -90,4 +91,4 @@ $(document).ready(function(){
$('html,body').animate({scrollTop: 0}, 1000); $('html,body').animate({scrollTop: 0}, 1000);
}); });
}); });

View file

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

View file

@ -1,181 +1,208 @@
;(function($) { ;(function($) {
"use strict"; 'use strict';
$.Browser = { $.Browser = {
defaults: { defaults: {
title: 'Choose Directory', title: 'Choose Directory (or enter manually)',
url: sbRoot + '/browser/', url: sbRoot + '/browser/',
autocompleteURL: sbRoot + '/browser/complete', autocompleteURL: sbRoot + '/browser/complete',
includeFiles: 0 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) { if (path === currentBrowserPath) {
return; return;
} }
currentBrowserPath = path; currentBrowserPath = path;
if (currentRequest) { if (currentRequest) {
currentRequest.abort(); currentRequest.abort();
} }
fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog busy'); fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog busy');
currentRequest = $.getJSON(endpoint, { path: path, includeFiles: includeFiles }, function (data) { currentRequest = $.getJSON(endpoint, {path: path, includeFiles: includeFiles}, function(data){
fileBrowserDialog.empty(); fileBrowserDialog.empty();
var first_val = data[0]; var firstVal = data[0], i = 0, list, link = null;
var i = 0; data = $.grep(data, function(){
var list, link = null; return i++ != 0;
data = $.grep(data, function (value) { });
return i++ != 0; $('<input type="text" class="form-control input-sm">')
}); .val(firstVal.currentPath)
$('<h2>').text(first_val.current_path).appendTo(fileBrowserDialog); .on('keypress', function(e){
list = $('<ul>').appendTo(fileBrowserDialog); if (13 === e.which) {
$.each(data, function (i, entry) { browse(e.target.value, endpoint, includeFiles);
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( .appendTo(fileBrowserDialog)
function () {$("span", this).addClass("ui-icon-folder-open"); }, .fileBrowser({showBrowseButton: !1})
function () {$("span", this).removeClass("ui-icon-folder-open"); } .on('autocompleteselect',
); function(e, ui){browse(ui.item.value, endpoint, includeFiles);
link.appendTo(list); });
});
$("a", list).wrap('<li class="ui-state-default ui-corner-all">');
fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog');
});
}
$.fn.nFileBrowser = function (callback, options) { list = $('<ul>').appendTo(fileBrowserDialog);
options = $.extend({}, $.Browser.defaults, options); $.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 (entry.isFile) {
if (!fileBrowserDialog) { 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 $.fn.nFileBrowser = function(callback, options){
fileBrowserDialog = $('<div id="fileBrowserDialog" style="display:hidden"></div>').appendTo('body').dialog({ options = $.extend({}, $.Browser.defaults, options);
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
});
}
fileBrowserDialog.dialog('option', 'buttons', [ // make a fileBrowserDialog object if one doesn't exist already
{ if (fileBrowserDialog) {
text: "Ok", fileBrowserDialog.dialog('option', 'title', options.title);
"class": "btn", } else {
click: function() { // set up the jquery dialog
// store the browsed path to the associated text field var docWidth = $(document).width(), dlgWidth = Math.min(docWidth - 80, 650),
callback(currentBrowserPath, options); docHeight = $(document).height() - 80, winHeight = $(window).height() - 80;
$(this).dialog("close"); fileBrowserDialog = $('<div id="fileBrowserDialog" style="display:none"></div>').appendTo('body').dialog({
} dialogClass: 'browserDialog',
}, title: options.title,
{ position: [(docWidth - dlgWidth)/2, 60],
text: "Cancel", minWidth: dlgWidth,
"class": "btn", height: Math.min(docHeight, winHeight),
click: function() { maxHeight: Math.min(docHeight, winHeight),
$(this).dialog("close"); maxWidth: docWidth - 80,
} modal: true,
} autoOpen: false
]); });
}
// set up the browser and launch the dialog fileBrowserDialog.dialog('option', 'buttons',
var initialDir = ''; [{
if (options.initialDir) { text: 'Ok',
initialDir = options.initialDir; '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); // set up the browser and launch the dialog
fileBrowserDialog.dialog('open'); var initialDir = '';
if (options.initialDir) {
initialDir = options.initialDir;
}
return false; browse(initialDir, options.url, options.includeFiles);
}; fileBrowserDialog.dialog('open');
$.fn.fileBrowser = function (options) { return false;
options = $.extend({}, $.Browser.defaults, options); };
// text field used for the result
options.field = $(this);
if (options.field.autocomplete && options.autocompleteURL) { $.fn.fileBrowser = function(options){
var query = ''; options = $.extend({}, $.Browser.defaults, options);
options.field.autocomplete({ // text field used for the result
position: { my : "top", at: "bottom", collision: "flipfit" }, options.field = $(this);
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);
};
}
var initialDir, path, callback, ls = false; if (options.field.autocomplete && options.autocompleteURL) {
// if the text field is empty and we're given a key then populate it with the last browsed value from localStorage var query = '';
try { ls = !!(localStorage.getItem); } catch (e) {} options.field.autocomplete({
if (ls && options.key) { position: {my: 'top', at: 'bottom', collision: 'flipfit'},
path = localStorage['fileBrowser-' + options.key]; source: function(request, response){
} //keep track of user submitted search term
if (options.key && options.field.val().length == 0 && (path)) { query = $.ui.autocomplete.escapeRegex(request.term, options.includeFiles);
options.field.val(path); $.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) { var path, callback, ls = false;
// store the browsed path to the associated text field // if empty text field and given a key then populate it with the last browsed value from localStorage
options.field.val(path); 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 callback = function(path, options){
if (ls && options.key) { // store the browsed path to the associated text field
localStorage['fileBrowser-' + options.key] = 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;
}
};
initialDir = options.field.val() || (options.key && path) || '';
options = $.extend(options, {initialDir: initialDir}); options.field.addClass('fileBrowserField');
if (options.showBrowseButton) {
// append the browse button and give it a click behaviour // 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 () { options.field.after(
$(this).nFileBrowser(callback, options); $('<input type="button" value="Browse&hellip;" class="btn btn-inline fileBrowser">').on('click',
return false; function(){
})); $(this).nFileBrowser(callback, $.extend(
}; {}, options, {initialDir: options.field.val() || (options.key && path) || ''}
));
return false;
}));
}
return options.field;
};
})(jQuery); })(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" />'; var loading = '<img src="' + sbRoot + '/images/loading16' + themeSpinner + '.gif" height="16" width="16" />';
$('#testGrowl').click(function () { $('#testGrowl').click(function () {
@ -352,37 +352,162 @@ $(document).ready(function(){
}); });
}); });
$('#testTrakt').click(function () { var elTraktAuth = $('#trakt-authenticate'), elTraktAuthResult = $('#trakt-authentication-result');
var trakt_api = $.trim($('#trakt_api').val());
var trakt_username = $.trim($('#trakt_username').val()); function trakt_send_auth(){
var trakt_password = $.trim($('#trakt_password').val()); var elAccountSelect = $('#trakt_accounts'), strCurAccountId = elAccountSelect.find('option:selected').val(),
if (!trakt_api || !trakt_username || !trakt_password) { elTraktPin = $('#trakt_pin'), strPin = $.trim(elTraktPin.val());
$('#testTrakt-result').html('Please fill out the necessary fields above.');
if (!trakt_api) { elTraktAuthResult.html(loading);
$('#trakt_api').addClass('warning');
} else { $.get(sbRoot + '/home/trakt_authenticate', {'pin': strPin, 'account': strCurAccountId})
$('#trakt_api').removeClass('warning'); .done(function(data) {
} elTraktAuth.prop('disabled', !1);
if (!trakt_username) { elTraktPin.val('');
$('#trakt_username').addClass('warning');
} else { var JSONData = $.parseJSON(data);
$('#trakt_username').removeClass('warning');
} elTraktAuthResult.html('Success' == JSONData.result
if (!trakt_password) { ? JSONData.result + ' account: ' + JSONData.account_name
$('#trakt_password').addClass('warning'); : JSONData.result + ' ' + JSONData.error_message);
} else {
$('#trakt_password').removeClass('warning'); if ('Success' == JSONData.result) {
} var elUpdateRows = $('#trakt-collection').find('tr');
return; if ('new' == strCurAccountId) {
} elAccountSelect.append($('<option>', {value: JSONData.account_id, text: JSONData.account_id + ' - ' + JSONData.account_name + ' (ok)'}));
$('#trakt_api,#trakt_username,#trakt_password').removeClass('warning');
$(this).prop('disabled', true); if ('Connect New Pin' == elUpdateRows.eq(0).find('th').last().text()) {
$('#testTrakt-result').html(loading); elUpdateRows.eq(0).find('th').last().html('Account');
$.get(sbRoot + '/home/testTrakt', {'api': trakt_api, 'username': trakt_username, 'password': trakt_password}) elUpdateRows.eq(1).find('th').last().html(JSONData.account_name);
.done(function (data) { elUpdateRows.eq(1).find('th').last().addClass('tid-' + JSONData.account_id);
$('#testTrakt-result').html(data); elUpdateRows.has('td').each(function(nRow) {
$('#testTrakt').prop('disabled', false); 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 () { $('#testEmail').click(function () {

View file

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

View file

@ -80,7 +80,6 @@ $(document).ready(function(){
$(torrent_seed_time_option).show(); $(torrent_seed_time_option).show();
} else if ('transmission' == selectedProvider){ } else if ('transmission' == selectedProvider){
client = 'Transmission'; client = 'Transmission';
$(torrent_seed_time_option).show();
$(torrent_high_bandwidth_option).show(); $(torrent_high_bandwidth_option).show();
$(torrent_label_option).hide(); $(torrent_label_option).hide();
//$('#directory_title').text(client + directory); //$('#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', []); $section.data('elements', []);
//create each 'step' DIV and add it to main Steps Container: //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] var $stepwords = ['first', 'then', 'finally'],
+ ' step') + '<div class="smalltext">' + $section.find('legend:eq(0)').text() + '<p></p></div>').appendTo($stepsguide); $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 //assign behavior to each step div
$thestep.click(function(){ $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) { if (0 === data.results.length) {
resultStr += '<span class="boldest">Sorry, no results found. Try a different search.</span>'; resultStr += '<span class="boldest">Sorry, no results found. Try a different search.</span>';
} else { } else {
var idxSrcDB = 0, idxSrcDBId = 1, idxSrcUrl = 2, idxShowID = 3, idxTitle = 4, idxDate = 5;
$.each(data.results, function (index, obj) { $.each(data.results, function (index, obj) {
checked = (0 == row ? ' checked' : ''); checked = (0 == row ? ' checked' : '');
rowType = (0 == row % 2 ? '' : ' class="alt"'); rowType = (0 == row % 2 ? '' : ' class="alt"');
row++; row++;
var whichSeries = cleanseText(obj.join('|'), !0), var display_show_name = cleanseText(obj[idxTitle], !0), showstartdate = '';
display_show_name = cleanseText(obj[4], !0),
showstartdate = '';
if (null !== obj[5]) { if (null !== obj[idxDate]) {
var startDate = new Date(obj[5]); var startDate = new Date(obj[idxDate]);
var today = new Date(); var today = new Date();
showstartdate = '&nbsp;<span class="stepone-result-date">(' showstartdate = '&nbsp;<span class="stepone-result-date">('
+ (startDate > today ? 'will debut' : 'started') + (startDate > today ? 'will debut' : 'started')
+ ' on ' + obj[5] + ')</span>'; + ': ' + obj[idxDate] + ')</span>';
} }
resultStr += '<div' + rowType + '>' resultStr += '<div' + rowType + '>'
+ '<input id="whichSeries" type="radio"' + '<input id="whichSeries" type="radio"'
+ ' class="stepone-result-radio"' + ' class="stepone-result-radio"'
+ ' title="Add show <span style=\'color: rgb(66, 139, 202)\'>' + display_show_name + '</span>"' + ' title="Add show <span style=\'color: rgb(66, 139, 202)\'>' + display_show_name + '</span>"'
+ ' name="whichSeries"' + ' name="whichSeries"'
+ ' value="' + whichSeries + '"' + ' value="' + cleanseText([obj[idxSrcDBId], obj[idxSrcDB], obj[idxShowID], obj[idxTitle]].join('|'), !0) + '"'
+ checked + checked
+ ' />' + ' />'
+ '<a' + '<a'
+ ' class="stepone-result-title"' + ' class="stepone-result-title"'
+ ' title="View detail for <span style=\'color: rgb(66, 139, 202)\'>' + display_show_name + '</span>"' + ' 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;"' + ' onclick="window.open(this.href, \'_blank\'); return false;"'
+ '>' + display_show_name + '</a>' + '>' + display_show_name + '</a>'
+ showstartdate + showstartdate
+ (null == obj[0] ? '' + (null == obj[idxSrcDB] ? ''
: '&nbsp;<span class="stepone-result-db grey-text">' + '[' + obj[0] + ']' + '</span>') : '&nbsp;<span class="stepone-result-db grey-text">' + '[' + obj[idxSrcDB] + ']' + '</span>')
+ '</div>' + "\n"; + '</div>' + "\n";
}); });
} }
@ -189,22 +187,24 @@ $(document).ready(function () {
function updateSampleText() { function updateSampleText() {
// if something's selected then we have some behavior to figure out // if something's selected then we have some behavior to figure out
var show_name, var show_name = '',
sep_char, sep_char,
elRadio = $('input:radio[name="whichSeries"]:checked'), elRadio = $('input:radio[name="whichSeries"]:checked'),
elInput = $('input:hidden[name="whichSeries"]'), elInput = $('input:hidden[name="whichSeries"]'),
elScene = $('#scene'),
elRootDirs = $('#rootDirs'), elRootDirs = $('#rootDirs'),
elFullShowPath = $('#fullShowPath'); elFullShowPath = $('#fullShowPath'),
idxWhichShowID = 2, idxWhichTitle = 3;
// if they've picked a radio button then use that // if they've picked a radio button then use that
if (elRadio.length) { 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 // if we provided a show in the hidden field, use that
else if (elInput.length && elInput.val().length) { else if (elInput.length && elInput.val().length) {
show_name = $('#providedName').val(); show_name = $('#providedName').val();
} else {
show_name = '';
} }
update_bwlist(show_name); update_bwlist(show_name);
var sample_text = '<p>Adding show <span class="show-name">' + cleanseText(show_name, !0) + '</span>' var sample_text = '<p>Adding show <span class="show-name">' + cleanseText(show_name, !0) + '</span>'

View file

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

View file

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
### BEGIN INIT INFO ### BEGIN INIT INFO
# Provides: sickbeard # Provides: sickgear
# Required-Start: $all # Required-Start: $all
# Required-Stop: $all # Required-Stop: $all
# Default-Start: 2 3 4 5 # Default-Start: 2 3 4 5
@ -13,27 +13,27 @@
# Source function library. # Source function library.
. /etc/init.d/functions . /etc/init.d/functions
# Source SickBeard configuration # Source SickGear configuration
if [ -f /etc/sysconfig/sickbeard ]; then if [ -f /etc/sysconfig/sickgear ]; then
. /etc/sysconfig/sickbeard . /etc/sysconfig/sickgear
fi fi
prog=sickbeard prog=sickgear
lockfile=/var/lock/subsys/$prog 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 ## the defaults
username=${SB_USER-sickbeard} username=${SG_USER-sickgear}
homedir=${SB_HOME-/opt/sickbeard} homedir=${SG_HOME-/opt/sickgear}
datadir=${SB_DATA-/opt/sickbeard} datadir=${SG_DATA-/opt/sickgear}
pidfile=${SB_PIDFILE-/var/run/sickbeard/sickbeard.pid} pidfile=${SG_PIDFILE-/var/run/sickgear/sickgear.pid}
nice=${SB_NICE-} nice=${SG_NICE-}
## ##
pidpath=`dirname ${pidfile}` pidpath=`dirname ${pidfile}`
options=" --daemon --nolaunch --pidfile=${pidfile} --datadir=${datadir}" 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 if [ ! -d $pidpath ]; then
mkdir -p $pidpath mkdir -p $pidpath
chown $username $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 # 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: # to work properly. Please create /etc/conf.d/sickbeard with the following:
# #
# SICKBEARD_USER=<user you want sickbeard to run under> # SICKGEAR_USER=<user you want sickgear to run under>
# SICKBEARD_GROUP=<group you want sickbeard to run under> # SICKGEAR_GROUP=<group you want sickgear to run under>
# SICKBEARD_DIR=<path to Sickbeard.py> # SICKGEAR_DIR=<path to Sickbeard.py>
# PATH_TO_PYTHON_2=/usr/bin/python2 # PATH_TO_PYTHON_2=/usr/bin/python2
# SICKBEARD_DATADIR=<directory that contains sickbeard.db file> # SICKGEAR_DATADIR=<directory that contains sickbeard.db file>
# SICKBEARD_CONFDIR=<directory that contains Sickbeard's config.ini file> # SICKGEAR_CONFDIR=<directory that contains SickGear's config.ini file>
# #
RUNDIR=/var/run/sickbeard RUNDIR=/var/run/sickgear
depend() { depend() {
need net need net
@ -30,32 +30,32 @@ get_pidfile() {
-e 's/[[:space:]]*$//' \ -e 's/[[:space:]]*$//' \
-e 's/^[[:space:]]*//' \ -e 's/^[[:space:]]*//' \
-e "s/^\(.*\)=\([^\"']*\)$/\1=\"\2\"/" \ -e "s/^\(.*\)=\([^\"']*\)$/\1=\"\2\"/" \
< ${SICKBEARD_CONFDIR}/config.ini \ < ${SICKGEAR_CONFDIR}/config.ini \
| sed -n -e "/^\[General\]/,/^\s*\[/{/^[^;].*\=.*/p;}"` | sed -n -e "/^\[General\]/,/^\s*\[/{/^[^;].*\=.*/p;}"`
echo "${RUNDIR}/sickbeard-${web_port}.pid" echo "${RUNDIR}/sickgear-${web_port}.pid"
} }
start() { 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 \ start-stop-daemon \
--quiet \ --quiet \
--start \ --start \
--user ${SICKBEARD_USER} \ --user ${SICKGEAR_USER} \
--group ${SICKBEARD_GROUP} \ --group ${SICKGEAR_GROUP} \
--name sickbeard \ --name sickgear \
--background \ --background \
--pidfile $(get_pidfile) \ --pidfile $(get_pidfile) \
--exec ${PATH_TO_PYTHON_2} \ --exec ${PATH_TO_PYTHON_2} \
-- \ -- \
${SICKBEARD_DIR}/SickBeard.py \ ${SICKGEAR_DIR}/SickBeard.py \
-d \ -d \
--pidfile $(get_pidfile) \ --pidfile $(get_pidfile) \
--config ${SICKBEARD_CONFDIR}/config.ini \ --config ${SICKGEAR_CONFDIR}/config.ini \
--datadir ${SICKBEARD_DATADIR} --datadir ${SICKGEAR_DATADIR}
eend $? eend $?
} }
@ -74,5 +74,5 @@ stop() {
local pidfile=$(get_pidfile) local pidfile=$(get_pidfile)
local rc local rc
ebegin "Stopping Sickbeard" ebegin "Stopping SickGear"
} }

View file

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

View file

@ -4,54 +4,54 @@
# #
# - Option names (e.g. ExecStart=, Type=) are case-sensitive) # - 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 # - Optional adjust EnvironmentFile= path to configuration file
# Can ONLY be used for configuring extra options used in ExecStart. # 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 # 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, # The FIRST token of the command line must be an ABSOLUTE FILE NAME,
# then followed by arguments for the process. # then followed by arguments for the process.
# If no --datadir is given, data is stored in same dir as SickBeard.py # If no --datadir is given, data is stored in same dir as SickBeard.py
# Arguments can also be set in EnvironmentFile (except python) # 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) # multi-user.target equates to runlevel 3 (multi-user text mode)
# graphical.target equates to runlevel 5 (multi-user X11 graphical 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 # Type=forking
# PIDFile=/var/run/sickbeard/sickbeard.pid # PIDFile=/var/run/sickgear/sickgear.pid
# ExecStart=/usr/bin/python /opt/sickbeard/SickBeard.py -q --daemon --nolaunch --pidfile=/var/run/sickbeard/sickbeard.pid --datadir=/opt/sickbeard # 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 # Type=forking
# GuessMainPID=no # 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 ### Example Using simple
# Type=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 # Type=simple
# EnvironmentFile=/etc/sickbeard.conf # EnvironmentFile=/etc/sickgear.conf
# ExecStart=/usr/bin/python /opt/sickbeard/SickBeard.py -q --nolaunch --datadir=${SB_DATA} # ExecStart=/usr/bin/python /opt/sickgear/SickBeard.py -q --nolaunch --datadir=${SB_DATA}
### Configuration ### Configuration
[Unit] [Unit]
Description=SickBeard Daemon Description=SickGear Daemon
[Service] [Service]
User=sickbeard User=sickgear
Group=sickbeard Group=sickgear
Type=forking Type=forking
GuessMainPID=no 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] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
### BEGIN INIT INFO ### BEGIN INIT INFO
# Provides: sickbeard # Provides: sickgear
# Required-Start: $local_fs $network $remote_fs # Required-Start: $local_fs $network $remote_fs
# Required-Stop: $local_fs $network $remote_fs # Required-Stop: $local_fs $network $remote_fs
# Should-Start: $NetworkManager # Should-Start: $NetworkManager
@ -12,55 +12,55 @@
# Description: starts instance of SickGear using start-stop-daemon # Description: starts instance of SickGear using start-stop-daemon
### END INIT INFO ### END INIT INFO
# Source SickBeard configuration # Source SickGear configuration
if [ -f /etc/default/sickbeard ]; then if [ -f /etc/default/sickgear ]; then
. /etc/default/sickbeard . /etc/default/sickgear
else else
echo "/etc/default/sickbeard not found using default settings."; echo "/etc/default/sickgear not found using default settings.";
fi fi
# Source init functions # Source init functions
. /lib/lsb/init-functions . /lib/lsb/init-functions
# Script name # Script name
NAME=sickbeard NAME=sickgear
# App name # App name
DESC=SickBeard DESC=SickGear
## Don't edit this file ## 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 ## SG_USER= #$RUN_AS, username to run sickgear under, the default is sickgear
## SB_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickbeard ## SG_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickgear
## SB_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickbeard ## SG_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickgear
## SB_PIDFILE= #$PID_FILE, the location of sickbeard.pid, the default is /var/run/sickbeard/sickbeard.pid ## 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 ## 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" ## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users"
## ##
## EXAMPLE if want to run as different user ## EXAMPLE if want to run as different user
## add SB_USER=username to /etc/default/sickbeard ## add SG_USER=username to /etc/default/sickgear
## otherwise default sickbeard is used ## otherwise default sickgear is used
## The defaults ## The defaults
# Run as username # Run as username
RUN_AS=${SB_USER-sickbeard} RUN_AS=${SG_USER-sickgear}
# Path to app SB_HOME=path_to_app_SickBeard.py # Path to app SG_HOME=path_to_app_SickBeard.py
APP_PATH=${SB_HOME-/opt/sickbeard} APP_PATH=${SG_HOME-/opt/sickgear}
# Data directory where sickbeard.db, cache and logs are stored # 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 # 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 # path to python bin
DAEMON=${PYTHON_BIN-/usr/bin/python} DAEMON=${PYTHON_BIN-/usr/bin/python}
# Extra daemon option like: SB_OPTS=" --config=/home/sickbeard/config.ini" # Extra daemon option like: SG_OPTS=" --config=/home/sickgear/config.ini"
EXTRA_DAEMON_OPTS=${SB_OPTS-} EXTRA_DAEMON_OPTS=${SG_OPTS-}
# Extra start-stop-daemon option like START_OPTS=" --group=users" # Extra start-stop-daemon option like START_OPTS=" --group=users"
EXTRA_SSD_OPTS=${SSD_OPTS-} EXTRA_SSD_OPTS=${SSD_OPTS-}
@ -75,7 +75,7 @@ test -x $DAEMON || exit 0
set -e 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 if [ ! -d $PID_PATH ]; then
mkdir -p $PID_PATH mkdir -p $PID_PATH
chown $RUN_AS $PID_PATH chown $RUN_AS $PID_PATH
@ -94,28 +94,28 @@ if [ -e $PID_FILE ]; then
fi fi
fi fi
start_sickbeard() { start_sickgear() {
echo "Starting $DESC" echo "Starting $DESC"
start-stop-daemon -d $APP_PATH -c $RUN_AS $EXTRA_SSD_OPTS --start --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS 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" echo "Stopping $DESC"
start-stop-daemon --stop --pidfile $PID_FILE --retry 15 start-stop-daemon --stop --pidfile $PID_FILE --retry 15
} }
case "$1" in case "$1" in
start) start)
start_sickbeard start_sickgear
;; ;;
stop) stop)
stop_sickbeard stop_sickgear
;; ;;
restart|force-reload) restart|force-reload)
stop_sickbeard stop_sickgear
sleep 2 sleep 2
start_sickbeard start_sickgear
;; ;;
status) status)
status_of_proc -p "$PID_FILE" "$DAEMON" "$DESC" 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._iamALIVE = False
self.counter = 0 # start with a throttled connection
self.counterAge = 0 self.counter = 6
self.counterAge = time()
def print_log(self, data): def print_log(self, data):
print(strftime("%Y-%m-%d %H:%M:%S", localtime(time())) + ": " + str(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)" __author__ = "Leonard Richardson (leonardr@segfault.org)"
__version__ = "4.3.2" __version__ = "4.4.0"
__copyright__ = "Copyright (c) 2004-2013 Leonard Richardson" __copyright__ = "Copyright (c) 2004-2015 Leonard Richardson"
__license__ = "MIT" __license__ = "MIT"
__all__ = ['BeautifulSoup'] __all__ = ['BeautifulSoup']
@ -77,10 +77,11 @@ class BeautifulSoup(Tag):
ASCII_SPACES = '\x20\x0a\x09\x0c\x0d' 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, 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 """The Soup object is initialized as the 'root tag', and the
provided markup (which can be a string or a file-like object) provided markup (which can be a string or a file-like object)
is fed into the underlying parser.""" is fed into the underlying parser."""
@ -156,8 +157,13 @@ class BeautifulSoup(Tag):
builder = builder_class() builder = builder_class()
if not (original_features == builder.NAME or if not (original_features == builder.NAME or
original_features in builder.ALTERNATE_NAMES): 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( warnings.warn(self.NO_PARSER_SPECIFIED_WARNING % dict(
parser=builder.NAME)) parser=builder.NAME,
markup_type=markup_type))
self.builder = builder self.builder = builder
self.is_xml = builder.is_xml self.is_xml = builder.is_xml
@ -202,7 +208,8 @@ class BeautifulSoup(Tag):
for (self.markup, self.original_encoding, self.declared_html_encoding, for (self.markup, self.original_encoding, self.declared_html_encoding,
self.contains_replacement_characters) in ( 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() self.reset()
try: try:
self._feed() self._feed()
@ -215,6 +222,16 @@ class BeautifulSoup(Tag):
self.markup = None self.markup = None
self.builder.soup = 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): def _feed(self):
# Convert the document to Unicode. # Convert the document to Unicode.
self.builder.reset() self.builder.reset()
@ -241,9 +258,7 @@ class BeautifulSoup(Tag):
def new_string(self, s, subclass=NavigableString): def new_string(self, s, subclass=NavigableString):
"""Create a new NavigableString associated with this soup.""" """Create a new NavigableString associated with this soup."""
navigable = subclass(s) return subclass(s)
navigable.setup()
return navigable
def insert_before(self, successor): def insert_before(self, successor):
raise NotImplementedError("BeautifulSoup objects don't support insert_before().") 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): def object_was_parsed(self, o, parent=None, most_recent_element=None):
"""Add an object to the parse tree.""" """Add an object to the parse tree."""
parent = parent or self.currentTag parent = parent or self.currentTag
most_recent_element = most_recent_element or self._most_recent_element previous_element = most_recent_element or self._most_recent_element
o.setup(parent, 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 self._most_recent_element = o
parent.contents.append(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): def _popToTag(self, name, nsprefix=None, inclusivePop=True):
"""Pops the tag stack up to and including the most recent """Pops the tag stack up to and including the most recent
instance of the given tag. If inclusivePop is false, pops the tag instance of the given tag. If inclusivePop is false, pops the tag

View file

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

View file

@ -2,6 +2,7 @@ __all__ = [
'HTML5TreeBuilder', 'HTML5TreeBuilder',
] ]
from pdb import set_trace
import warnings import warnings
from bs4.builder import ( from bs4.builder import (
PERMISSIVE, PERMISSIVE,
@ -9,7 +10,10 @@ from bs4.builder import (
HTML_5, HTML_5,
HTMLTreeBuilder, HTMLTreeBuilder,
) )
from bs4.element import NamespacedAttribute from bs4.element import (
NamespacedAttribute,
whitespace_re,
)
import html5lib import html5lib
from html5lib.constants import namespaces from html5lib.constants import namespaces
from bs4.element import ( from bs4.element import (
@ -26,9 +30,16 @@ class HTML5TreeBuilder(HTMLTreeBuilder):
features = [NAME, PERMISSIVE, HTML_5, HTML] 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. # Store the user-specified encoding for use later on.
self.user_specified_encoding = user_specified_encoding 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) yield (markup, None, None, False)
# These methods are defined by Beautiful Soup. # These methods are defined by Beautiful Soup.
@ -103,7 +114,13 @@ class AttrList(object):
def __iter__(self): def __iter__(self):
return list(self.attrs.items()).__iter__() return list(self.attrs.items()).__iter__()
def __setitem__(self, name, value): 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 self.element[name] = value
def items(self): def items(self):
return list(self.attrs.items()) return list(self.attrs.items())
@ -180,6 +197,7 @@ class Element(html5lib.treebuilders._base.Node):
return AttrList(self.element) return AttrList(self.element)
def setAttributes(self, attributes): def setAttributes(self, attributes):
if attributes is not None and len(attributes) > 0: if attributes is not None and len(attributes) > 0:
converted_attributes = [] converted_attributes = []
@ -226,6 +244,9 @@ class Element(html5lib.treebuilders._base.Node):
def reparentChildren(self, new_parent): def reparentChildren(self, new_parent):
"""Move all of this tag's children into another tag.""" """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 element = self.element
new_parent_element = new_parent.element new_parent_element = new_parent.element
# Determine what this tag's next_element will be once all the children # 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 new_parents_last_descendant_next_element = new_parent_element.next_element
to_append = element.contents to_append = element.contents
append_after = new_parent.element.contents append_after = new_parent_element.contents
if len(to_append) > 0: if len(to_append) > 0:
# Set the first child's previous_element and previous_sibling # Set the first child's previous_element and previous_sibling
# to elements within the new parent # to elements within the new parent
first_child = to_append[0] 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 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 # Fix the last child's next_element and next_sibling
last_child = to_append[-1] last_child = to_append[-1]
last_child.next_element = new_parents_last_descendant_next_element 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 last_child.next_sibling = None
for child in to_append: for child in to_append:
@ -265,6 +297,10 @@ class Element(html5lib.treebuilders._base.Node):
element.contents = [] element.contents = []
element.next_element = final_next_element element.next_element = final_next_element
# print "DONE WITH MOVE"
# print "FROM", self.element
# print "TO", new_parent_element
def cloneNode(self): def cloneNode(self):
tag = self.soup.new_tag(self.element.name, self.namespace) tag = self.soup.new_tag(self.element.name, self.namespace)
node = Element(tag, self.soup, self.namespace) node = Element(tag, self.soup, self.namespace)

View file

@ -4,10 +4,16 @@ __all__ = [
'HTMLParserTreeBuilder', 'HTMLParserTreeBuilder',
] ]
from HTMLParser import ( from HTMLParser import HTMLParser
HTMLParser,
HTMLParseError, 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 sys
import warnings import warnings
@ -20,8 +26,10 @@ import warnings
# strict=True works well on Python 3.2.2. # strict=True works well on Python 3.2.2.
major, minor, release = sys.version_info[:3] major, minor, release = sys.version_info[:3]
CONSTRUCTOR_TAKES_STRICT = major == 3 and minor == 2 and release >= 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 CONSTRUCTOR_TAKES_CONVERT_CHARREFS = major == 3 and minor >= 4
from bs4.element import ( from bs4.element import (
CData, CData,
Comment, Comment,
@ -119,18 +127,19 @@ class BeautifulSoupHTMLParser(HTMLParser):
class HTMLParserTreeBuilder(HTMLTreeBuilder): class HTMLParserTreeBuilder(HTMLTreeBuilder):
is_xml = False is_xml = False
picklable = True
NAME = HTMLPARSER NAME = HTMLPARSER
features = [NAME, HTML, STRICT] features = [NAME, HTML, STRICT]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if CONSTRUCTOR_TAKES_STRICT: if CONSTRUCTOR_TAKES_STRICT and not CONSTRUCTOR_STRICT_IS_DEPRECATED:
kwargs['strict'] = False kwargs['strict'] = False
if CONSTRUCTOR_TAKES_CONVERT_CHARREFS: if CONSTRUCTOR_TAKES_CONVERT_CHARREFS:
kwargs['convert_charrefs'] = False kwargs['convert_charrefs'] = False
self.parser_args = (args, kwargs) self.parser_args = (args, kwargs)
def prepare_markup(self, markup, user_specified_encoding=None, 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 :return: A 4-tuple (markup, original encoding, encoding
declared within markup, whether any characters had to be declared within markup, whether any characters had to be
@ -141,7 +150,8 @@ class HTMLParserTreeBuilder(HTMLTreeBuilder):
return return
try_encodings = [user_specified_encoding, document_declared_encoding] 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, yield (dammit.markup, dammit.original_encoding,
dammit.declared_html_encoding, dammit.declared_html_encoding,
dammit.contains_replacement_characters) dammit.contains_replacement_characters)

View file

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

View file

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

View file

@ -33,12 +33,21 @@ def diagnose(data):
if 'lxml' in basic_parsers: if 'lxml' in basic_parsers:
basic_parsers.append(["lxml", "xml"]) basic_parsers.append(["lxml", "xml"])
from lxml import etree try:
print "Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION)) 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: if 'html5lib' in basic_parsers:
import html5lib try:
print "Found html5lib version %s" % html5lib.__version__ 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'): if hasattr(data, 'read'):
data = data.read() data = data.read()

View file

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

View file

@ -28,7 +28,6 @@
import logging import logging
import re import re
from io import BytesIO
from .enums import ProbingState from .enums import ProbingState
@ -79,16 +78,16 @@ class CharSetProber(object):
This filter applies to all scripts which do not use English characters. 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 # This regex expression filters out only words that have at-least one
# international character. The word may include one marker character at # international character. The word may include one marker character at
# the end. # the end.
words = re.findall( words = re.findall(b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?',
b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?', buf) buf)
for word in words: 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 # 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 # space as markers shouldn't affect our analysis (they are used
@ -97,9 +96,9 @@ class CharSetProber(object):
last_char = word[-1:] last_char = word[-1:]
if not last_char.isalpha() and last_char < b'\x80': if not last_char.isalpha() and last_char < b'\x80':
last_char = b' ' last_char = b' '
filtered.write(last_char) filtered.extend(last_char)
return filtered.getvalue() return filtered
@staticmethod @staticmethod
def filter_with_english_letters(buf): def filter_with_english_letters(buf):
@ -113,7 +112,7 @@ class CharSetProber(object):
characters and extended ASCII characters, but is currently only used by characters and extended ASCII characters, but is currently only used by
``Latin1Prober``. ``Latin1Prober``.
""" """
filtered = BytesIO() filtered = bytearray()
in_tag = False in_tag = False
prev = 0 prev = 0
@ -132,15 +131,15 @@ class CharSetProber(object):
if curr > prev and not in_tag: if curr > prev and not in_tag:
# Keep everything after last non-extended-ASCII, # Keep everything after last non-extended-ASCII,
# non-alphabetic character # non-alphabetic character
filtered.write(buf[prev:curr]) filtered.extend(buf[prev:curr])
# Output a space to delimit stretch we kept # Output a space to delimit stretch we kept
filtered.write(b' ') filtered.extend(b' ')
prev = curr + 1 prev = curr + 1
# If we're not in a tag... # If we're not in a tag...
if not in_tag: if not in_tag:
# Keep everything after last non-extended-ASCII, non-alphabetic # Keep everything after last non-extended-ASCII, non-alphabetic
# character # 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. 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(object):
class InputState(IntEnum):
""" """
This enum represents the different states a universal detector can be in. This enum represents the different states a universal detector can be in.
""" """
@ -19,7 +14,7 @@ class InputState(IntEnum):
high_byte = 2 high_byte = 2
class LanguageFilter(IntEnum): class LanguageFilter(object):
""" """
This enum represents the different language filters we can apply to a This enum represents the different language filters we can apply to a
``UniversalDetector``. ``UniversalDetector``.
@ -34,7 +29,7 @@ class LanguageFilter(IntEnum):
cjk = chinese | japanese | korean cjk = chinese | japanese | korean
class ProbingState(IntEnum): class ProbingState(object):
""" """
This enum represents the different states a prober can be in. This enum represents the different states a prober can be in.
""" """
@ -43,7 +38,7 @@ class ProbingState(IntEnum):
not_me = 2 not_me = 2
class MachineState(IntEnum): class MachineState(object):
""" """
This enum represents the different states a state machine can be in. 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) Ibm866Model, Ibm855Model)
from .langgreekmodel import Latin7GreekModel, Win1253GreekModel from .langgreekmodel import Latin7GreekModel, Win1253GreekModel
from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel
from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel # from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel
from .langthaimodel import TIS620ThaiModel from .langthaimodel import TIS620ThaiModel
from .langhebrewmodel import Win1255HebrewModel from .langhebrewmodel import Win1255HebrewModel
from .hebrewprober import HebrewProber from .hebrewprober import HebrewProber
@ -63,9 +63,9 @@ class SBCSGroupProber(CharSetGroupProber):
] ]
hebrew_prober = HebrewProber() hebrew_prober = HebrewProber()
logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel,
False, hebrew_prober) False, hebrew_prober)
visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True, visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True,
hebrew_prober) hebrew_prober)
hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober) hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober)
self.probers.extend([hebrew_prober, logical_hebrew_prober, self.probers.extend([hebrew_prober, logical_hebrew_prober,
visual_hebrew_prober]) visual_hebrew_prober])

View file

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

View file

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

View file

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

View file

@ -16,14 +16,14 @@ from dateutil.tz import tzfile
__all__ = ["gettz", "gettz_db_metadata", "rebuild"] __all__ = ["gettz", "gettz_db_metadata", "rebuild"]
_ZONEFILENAME = "dateutil-zoneinfo.tar.gz" ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
_METADATA_FN = 'METADATA' METADATA_FN = 'METADATA'
# python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but # python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but
# it's close enough for python2.6 # it's close enough for python2.6
_tar_open = TarFile.open tar_open = TarFile.open
if not hasattr(TarFile, '__exit__'): if not hasattr(TarFile, '__exit__'):
def _tar_open(*args, **kwargs): def tar_open(*args, **kwargs):
return closing(TarFile.open(*args, **kwargs)) return closing(TarFile.open(*args, **kwargs))
@ -34,7 +34,7 @@ class tzfile(tzfile):
def getzoneinfofile_stream(): def getzoneinfofile_stream():
try: try:
return BytesIO(get_data(__name__, _ZONEFILENAME)) return BytesIO(get_data(__name__, ZONEFILENAME))
except IOError as e: # TODO switch to FileNotFoundError? except IOError as e: # TODO switch to FileNotFoundError?
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
return None return None
@ -43,7 +43,7 @@ def getzoneinfofile_stream():
class ZoneInfoFile(object): class ZoneInfoFile(object):
def __init__(self, zonefile_stream=None): def __init__(self, zonefile_stream=None):
if zonefile_stream is not 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 # dict comprehension does not work on python2.6
# TODO: get back to the nicer syntax when we ditch python2.6 # TODO: get back to the nicer syntax when we ditch python2.6
# self.zones = {zf.name: tzfile(tf.extractfile(zf), # self.zones = {zf.name: tzfile(tf.extractfile(zf),
@ -52,7 +52,7 @@ class ZoneInfoFile(object):
self.zones = dict((zf.name, tzfile(tf.extractfile(zf), self.zones = dict((zf.name, tzfile(tf.extractfile(zf),
filename=zf.name)) filename=zf.name))
for zf in tf.getmembers() 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 # deal with links: They'll point to their parent object. Less
# waste of memory # waste of memory
# links = {zl.name: self.zones[zl.linkname] # links = {zl.name: self.zones[zl.linkname]
@ -62,7 +62,7 @@ class ZoneInfoFile(object):
zl.islnk() or zl.issym()) zl.islnk() or zl.issym())
self.zones.update(links) self.zones.update(links)
try: 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') metadata_str = metadata_json.read().decode('UTF-8')
self.metadata = json.loads(metadata_str) self.metadata = json.loads(metadata_str)
except KeyError: except KeyError:
@ -100,36 +100,3 @@ def gettz_db_metadata():
return _CLASS_ZONE_INSTANCE[0].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 hachoir_core.tools import humanDurationNanosec
from lib.hachoir_core.i18n import _ from hachoir_core.i18n import _
from math import floor from math import floor
from time import time 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. string, number, hexadecimal, etc.
""" """
from lib.hachoir_core.endian import BIG_ENDIAN, LITTLE_ENDIAN from hachoir_core.endian import BIG_ENDIAN, LITTLE_ENDIAN, MIDDLE_ENDIAN
from lib.hachoir_core.compatibility import reversed from hachoir_core.compatibility import reversed
from itertools import chain, repeat from itertools import chain, repeat
from struct import calcsize, unpack, error as struct_error from struct import calcsize, unpack, error as struct_error
@ -30,6 +30,28 @@ def swap32(value):
| ((value & 0x00FF0000L) >> 8) \ | ((value & 0x00FF0000L) >> 8) \
| ((value & 0xFF000000L) >> 24) | ((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): def bin2long(text, endian):
""" """
Convert binary number written in a string into an integer. 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) assert endian in (LITTLE_ENDIAN, BIG_ENDIAN)
bits = [ (ord(character)-ord("0")) \ bits = [ (ord(character)-ord("0")) \
for character in text if character in "01" ] for character in text if character in "01" ]
assert len(bits) != 0
if endian is not BIG_ENDIAN: if endian is not BIG_ENDIAN:
bits = reversed(bits) bits = bits[::-1]
size = len(bits)
assert 0 < size
value = 0 value = 0
for bit in bits: for bit in bits:
value *= 2 value *= 2
@ -142,7 +165,7 @@ def long2raw(value, endian, size=None):
'\x19\x12\x00\x00' '\x19\x12\x00\x00'
""" """
assert (not size and 0 < value) or (0 <= value) 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 = [] text = []
while (value != 0 or text == ""): while (value != 0 or text == ""):
byte = value % 256 byte = value % 256
@ -153,13 +176,15 @@ def long2raw(value, endian, size=None):
else: else:
need = 0 need = 0
if need: if need:
if endian is BIG_ENDIAN: if endian is LITTLE_ENDIAN:
text = chain(repeat("\0", need), reversed(text))
else:
text = chain(text, repeat("\0", need)) text = chain(text, repeat("\0", need))
else:
text = chain(repeat("\0", need), reversed(text))
else: else:
if endian is BIG_ENDIAN: if endian is not LITTLE_ENDIAN:
text = reversed(text) text = reversed(text)
if endian is MIDDLE_ENDIAN:
text = arrswapmid(text)
return "".join(text) return "".join(text)
def long2bin(size, value, endian, classic_mode=False): def long2bin(size, value, endian, classic_mode=False):
@ -257,6 +282,8 @@ def str2long(data, endian):
True True
>>> str2long("\xff\xff\xff\xff\xff\xff\xff\xff", BIG_ENDIAN) == (2**64-1) >>> str2long("\xff\xff\xff\xff\xff\xff\xff\xff", BIG_ENDIAN) == (2**64-1)
True True
>>> str2long("\x0b\x0a\x0d\x0c", MIDDLE_ENDIAN) == 0x0a0b0c0d
True
""" """
assert 1 <= len(data) <= 32 # arbitrary limit: 256 bits assert 1 <= len(data) <= 32 # arbitrary limit: 256 bits
try: try:
@ -264,14 +291,15 @@ def str2long(data, endian):
except KeyError: except KeyError:
pass pass
assert endian in (BIG_ENDIAN, LITTLE_ENDIAN) assert endian in (BIG_ENDIAN, LITTLE_ENDIAN, MIDDLE_ENDIAN)
shift = 0 shift = 0
value = 0 value = 0
if endian is BIG_ENDIAN: if endian is BIG_ENDIAN:
data = reversed(data) data = reversed(data)
elif endian is MIDDLE_ENDIAN:
data = reversed(strswapmid(data))
for character in data: for character in data:
byte = ord(character) byte = ord(character)
value += (byte << shift) value += (byte << shift)
shift += 8 shift += 8
return value return value

View file

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

View file

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

View file

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

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