diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..176a458f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore index 5ef2beed..b22d691a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,8 +28,12 @@ ehthumbs.db Icon? Thumbs.db -#ignore thumbnails created by windows -Thumbs.db +#Ignore files generated by PyCharm +.idea/* + +#Ignore files generated by vi +*.swp + #Ignore files build by Visual Studio *.obj *.exe diff --git a/README.md b/README.md index 168a76c4..d1e0af34 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,65 @@ -#![preview thumb](https://github.com/rembo10/headphones/raw/master/data/images/headphoneslogo.png)Headphones - -###Installation and Notes - -This is a relatively early release of a third-party add-on for SABnzbd. It's been around for about a year, and while -quite functional, there are still a lot of kinks to work out! - -To run it, just double-click the Headphones.py file (in Windows - you may need to right click and click 'Open With' -> Python) or launch a terminal, cd into the Headphones directory and run 'python headphones.py'. - -For additional startup options, type 'python Headphones.py -h' or 'python Headphones.py --help' - -###Screenshots - -Homepage (Artist Overview) - -![preview thumb](http://i.imgur.com/LZO9a.png) - -One of the many settings pages.... - -![preview thumb](http://i.imgur.com/xcWNy.png) - -It might even know you better than you know yourself: - -![preview thumb](http://i.imgur.com/R7J0f.png) - -Import Your Favorite Artists: - -![preview thumb](http://i.imgur.com/6tZoC.png) - -Artist Search Results (also search by album!): - -![preview thumb](http://i.imgur.com/rIV0P.png) - -Artist Page with Bio & Album Overview: - -![preview thumb](http://i.imgur.com/SSil1.png) - -Album Page with track overview: - -![preview thumb](http://i.imgur.com/kcjES.png) - -If you run into any issues, use the GitHub issue tracker at: http://github.com/rembo10/headphones/issues. - -This is free software under the GPL v3 open source license - so feel free to do with it what you wish. +#![preview thumb](https://github.com/rembo10/headphones/raw/master/data/images/headphoneslogo.png)Headphones + +###Support & Discuss + +You are free to join the HP support community on IRC where you can ask questions, hang around and discuss anything related to HP. + +1. Use any IRC client and connect to the Freenode server. +2. Join #headphones + +###Installation and Notes + +[Read our Wiki](../../wiki) on how to install and use HeadPhones properly. + +[**Troubleshooting** page](../../wiki/TroubleShooting) in the wiki can help you with comon problems. + +**Issues** can be reported on the GitHub issue tracker considering these rules: + +1. Analyze your log, you just might find the solution yourself! +2. You read the wiki and searched existing issues, but this is not solving your problem. +3. Post the issue with a clear title, description and the HP log and use [proper markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your text (code/log in code blocks). +4. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it. + +**Feature requests** can be reported on the GitHub issue tracker too: + +1. Search for similar existing 'issues', feature requests can be recognized by the label 'Request'. +2. If a similar Request exists, post a comment (+1, or add a new idea to the existing request), otherwise you can create a new one. + +If you **comply with these rules** you can [post your request/issue](http://github.com/rembo10/headphones/issues). + +**Support** the project by implementing new features, solving support tickets and provide bug fixes. +If you change something in the code always make a PR to the developer branch instead of the master branch. + + +###Screenshots + +Homepage (Artist Overview) + +![preview thumb](http://i.imgur.com/LZO9a.png) + +One of the many settings pages.... + +![preview thumb](http://i.imgur.com/xcWNy.png) + +It might even know you better than you know yourself: + +![preview thumb](http://i.imgur.com/R7J0f.png) + +Import Your Favorite Artists: + +![preview thumb](http://i.imgur.com/6tZoC.png) + +Artist Search Results (also search by album!): + +![preview thumb](http://i.imgur.com/rIV0P.png) + +Artist Page with Bio & Album Overview: + +![preview thumb](http://i.imgur.com/SSil1.png) + +Album Page with track overview: + +![preview thumb](http://i.imgur.com/kcjES.png) + + +This is free software under the GPL v3 open source license - so feel free to do with it what you wish. diff --git a/data/interfaces/default/album.html b/data/interfaces/default/album.html index ba8bf33b..f6edaf0b 100644 --- a/data/interfaces/default/album.html +++ b/data/interfaces/default/album.html @@ -89,9 +89,15 @@
- -

${album['AlbumTitle']}

-

${album['ArtistName']}

+ +

+ ${album['AlbumTitle']} +

+ +

+ ${album['ArtistName']} +

+ <% totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0] totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']])) @@ -275,9 +281,58 @@ feedback.fadeIn(); } + var loadingMessage = false; + var spinner_active = false; + var loadingtext_active = false; + var refreshInterval; + var wasLoading = false; + var x = 0; + + function checkAlbumStatus() { + $.getJSON("getAlbumjson?AlbumID=${album['AlbumID']}", function(data) { + if (data['Status'] == "Loading"){ + wasLoading = true; + $('#albumnamelink').text(data["AlbumTitle"]); + $('#artistnamelink').text(data["ArtistName"]); + if (loadingMessage == false){ + $("#ajaxMsg").after( "
" ); + showArtistMsg("Getting album information"); + loadingMessage = true; + } + if (spinner_active == false){ + $('#albumname').prepend('') + spinner_active = true; + } + if (loadingtext_active == false){ + $('#albumname').append('

(Album information is currently being loaded)

') + loadingtext_active = true; + } + } + else{ + if (++x === 10) { + clearInterval(refreshInterval); + } + var sts = $("#artistname").text(); + if (wasLoading == true || sts == "Loading"){ + location.reload(); + $('#albumnamespinner').remove() + $('#loadingtext').remove() + $('#ajaxMsg2').remove() + spinner_active = false + loadingtext_active = false + loadingMessage = false + } + } + }); + } + $(document).ready(function() { getAlbumInfo(); initThisPage(); + checkAlbumStatus(); + refreshInterval = setInterval(function(){ + checkAlbumStatus(); + }, 3000); }); diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index e606a02b..eba89c37 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -58,11 +58,11 @@
- - +
@@ -115,3 +115,25 @@ <%def name="javascriptIncludes()"> <%def name="headIncludes()"> <%def name="headerIncludes()"> + + + diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index f832b35d..a05578f0 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -362,22 +362,6 @@ -
- NZBsRus -
- -
-
-
- - -
-
- - -
-
-
omgwtfnzbs
@@ -390,7 +374,7 @@
- +
@@ -407,14 +391,17 @@ -
- -
-
+
- + +
+
+
+ + +
@@ -480,12 +467,14 @@
Preferred Bitrate: kbps
- Reject if less than % or more than % of the target size (leave blank for no limit)

-
+ + Reject if less than % or more than % of the target size (leave blank for no limit)
+
+
-
+
@@ -1752,9 +1741,9 @@ initActions(); initConfigCheckbox("#use_headphones_indexer"); initConfigCheckbox("#usenewznab"); - initConfigCheckbox("#usenzbsrus"); initConfigCheckbox("#usenzbsorg"); initConfigCheckbox("#useomgwtfnzbs"); + initConfigCheckbox("#usekat"); initConfigCheckbox("#usepiratebay"); initConfigCheckbox("#usewaffles"); initConfigCheckbox("#userutracker"); diff --git a/data/interfaces/default/css/config.less b/data/interfaces/default/css/config.less index 4fdccaee..022a0700 100644 --- a/data/interfaces/default/css/config.less +++ b/data/interfaces/default/css/config.less @@ -1,4 +1,4 @@ -/* Variables */ +/* Variables */ @base-font-face: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; @alt-font-face: "Trebuchet MS", Helvetica, Arial, sans-serif; @base-font-size: 12px; @@ -10,20 +10,20 @@ @msg-bg: #FFF6A9; @msg-bg-success: #D3FFD7; @msg-bg-error: #FFD3D3; - -/* Mixins */ -.rounded(@radius: 5px) { - -moz-border-radius: @radius; - -webkit-border-radius: @radius; - border-radius: @radius; -} -.roundedTop(@radius: 5px) { - -moz-border-radius-topleft: @radius; - -moz-border-radius-topright: @radius; - -webkit-border-top-right-radius: @radius; - -webkit-border-top-left-radius: @radius; - border-top-left-radius: @radius; - border-top-right-radius: @radius; + +/* Mixins */ +.rounded(@radius: 5px) { + -moz-border-radius: @radius; + -webkit-border-radius: @radius; + border-radius: @radius; +} +.roundedTop(@radius: 5px) { + -moz-border-radius-topleft: @radius; + -moz-border-radius-topright: @radius; + -webkit-border-top-right-radius: @radius; + -webkit-border-top-left-radius: @radius; + border-top-left-radius: @radius; + border-top-right-radius: @radius; } .roundedLeftTop(@radius: 5px) { -moz-border-radius-topleft: @radius; @@ -34,14 +34,14 @@ -moz-border-radius-topright: @radius; -webkit-border-top-right-radius: @radius; border-top-right-radius: @radius; -} -.roundedBottom(@radius: 5px) { - -moz-border-radius-bottomleft: @radius; - -moz-border-radius-bottomright: @radius; - -webkit-border-bottom-right-radius: @radius; - -webkit-border-bottom-left-radius: @radius; - border-bottom-left-radius: @radius; - border-bottom-right-radius: @radius; +} +.roundedBottom(@radius: 5px) { + -moz-border-radius-bottomleft: @radius; + -moz-border-radius-bottomright: @radius; + -webkit-border-bottom-right-radius: @radius; + -webkit-border-bottom-left-radius: @radius; + border-bottom-left-radius: @radius; + border-bottom-right-radius: @radius; } .roundedLeftBottom(@radius: 5px) { -moz-border-radius-bottomleft: @radius; @@ -52,7 +52,7 @@ -moz-border-radius-bottomright: @radius; -webkit-border-bottom-right-radius: @radius; border-bottom-right-radius: @radius; -} +} .shadow(@shadow: 0 17px 11px -1px #ced8d9) { -moz-box-shadow: @shadow; -webkit-box-shadow: @shadow; @@ -74,4 +74,4 @@ -o-opacity:@opacity_percent / 100 !important; opacity:@opacity_percent / 100 !important; } - + diff --git a/data/interfaces/default/css/style.css b/data/interfaces/default/css/style.css index 6789726f..8bb4c597 100644 --- a/data/interfaces/default/css/style.css +++ b/data/interfaces/default/css/style.css @@ -503,7 +503,7 @@ header .wrapper { margin: 0 auto; overflow: hidden; position: relative; - width: 960px; + width: 990px; } header #logo { float: left; @@ -758,7 +758,7 @@ div#searchbar input[type=text] { line-height: normal; margin-right: 5px; padding: 4px 5px 4px 25px; - width: 150px; + width: 185px; } div#searchbar .mini-icon { color: #999; @@ -1126,13 +1126,34 @@ div#artistheader h2 a { padding: 2px 10px; } #searchresults_table th#albumname { - min-width: 225px; + min-width: 250px; text-align: left; + font-size: 14px; } #searchresults_table th#artistname { min-width: 325px; text-align: left; } +#searchresults_table th#artistnamesmall { + min-width: 125px; + text-align: left; + font-size: 14px; +} +#searchresults_table th#reldate { + min-width: 80px; + text-align: center; + font-size: 14px; +} +#searchresults_table th#format { + min-width: 50px; + text-align: left; + font-size: 14px; +} +#searchresults_table th#tracks { + min-width: 50px; + text-align: left; + font-size: 14px; +} #searchresults_table #artistImg { background: url("../images/loader_black.gif") no-repeat scroll center center #ffffff; border: 3px solid #FFFFFF; @@ -1144,15 +1165,40 @@ div#artistheader h2 a { width: 50px; } #searchresults_table td#albumname { - min-width: 200px; + min-width: 250px; text-align: left; vertical-align: middle; + font-size: 14px; } #searchresults_table td#artistname { min-width: 300px; text-align: left; vertical-align: middle; } +#searchresults_table td#artistnamesmall { + min-width: 100px; + text-align: left; + vertical-align: middle; + font-size: 14px; +} +#searchresults_table td#reldate { + min-width: 80px; + text-align: center; + vertical-align: middle; + font-size: 14px; +} +#searchresults_table td#format { + min-width: 50px; + text-align: left; + vertical-align: middle; + font-size: 14px; +} +#searchresults_table td#tracks { + min-width: 50px; + text-align: left; + vertical-align: middle; + font-size: 14px; +} #searchresults_table td#add { vertical-align: middle; } @@ -1416,6 +1462,11 @@ div#artistheader h3 span { min-width: 75px; text-align: center; } +#searchresults_table th#scoresmall { + min-width: 50px; + text-align: center; + font-size: 14px; +} #track_table td#bitrate, #track_table td#format { font-size: 12px; diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index b7cef364..45ceb31d 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -1,6 +1,7 @@ <%inherit file="base.html"/> <%! from headphones import helpers + import cgi %> <%def name="headerIncludes()"> @@ -51,11 +52,11 @@ %> ${item['DateAdded']} - ${item['Title']} [${fileid}][album page] + ${cgi.escape(item['Title'], quote=True)} [${fileid}][album page] ${helpers.bytes_to_mb(item['Size'])} ${item['Status']} - [retry][new] - + [retry][new] + %endfor diff --git a/data/interfaces/default/images/MusicBrainz_Album_Icon.png b/data/interfaces/default/images/MusicBrainz_Album_Icon.png new file mode 100644 index 00000000..16abf29b Binary files /dev/null and b/data/interfaces/default/images/MusicBrainz_Album_Icon.png differ diff --git a/data/interfaces/default/images/MusicBrainz_Artist_Icon.png b/data/interfaces/default/images/MusicBrainz_Artist_Icon.png new file mode 100644 index 00000000..82e3381d Binary files /dev/null and b/data/interfaces/default/images/MusicBrainz_Artist_Icon.png differ diff --git a/data/interfaces/default/js/fancybox/jquery.fancybox-1.3.4.js b/data/interfaces/default/js/fancybox/jquery.fancybox-1.3.4.js index be772753..a8520051 100644 --- a/data/interfaces/default/js/fancybox/jquery.fancybox-1.3.4.js +++ b/data/interfaces/default/js/fancybox/jquery.fancybox-1.3.4.js @@ -1,1156 +1,1156 @@ -/* - * FancyBox - jQuery Plugin - * Simple and fancy lightbox alternative - * - * Examples and documentation at: http://fancybox.net - * - * Copyright (c) 2008 - 2010 Janis Skarnelis - * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. - * - * Version: 1.3.4 (11/11/2010) - * Requires: jQuery v1.3+ - * - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - */ - -;(function($) { - var tmp, loading, overlay, wrap, outer, content, close, title, nav_left, nav_right, - - selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [], - - ajaxLoader = null, imgPreloader = new Image(), imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i, swfRegExp = /[^\.]\.(swf)\s*$/i, - - loadingTimer, loadingFrame = 1, - - titleHeight = 0, titleStr = '', start_pos, final_pos, busy = false, fx = $.extend($('
')[0], { prop: 0 }), - - isIE6 = $.browser.msie && $.browser.version < 7 && !window.XMLHttpRequest, - - /* - * Private methods - */ - - _abort = function() { - loading.hide(); - - imgPreloader.onerror = imgPreloader.onload = null; - - if (ajaxLoader) { - ajaxLoader.abort(); - } - - tmp.empty(); - }, - - _error = function() { - if (false === selectedOpts.onError(selectedArray, selectedIndex, selectedOpts)) { - loading.hide(); - busy = false; - return; - } - - selectedOpts.titleShow = false; - - selectedOpts.width = 'auto'; - selectedOpts.height = 'auto'; - - tmp.html( '

The requested content cannot be loaded.
Please try again later.

' ); - - _process_inline(); - }, - - _start = function() { - var obj = selectedArray[ selectedIndex ], - href, - type, - title, - str, - emb, - ret; - - _abort(); - - selectedOpts = $.extend({}, $.fn.fancybox.defaults, (typeof $(obj).data('fancybox') == 'undefined' ? selectedOpts : $(obj).data('fancybox'))); - - ret = selectedOpts.onStart(selectedArray, selectedIndex, selectedOpts); - - if (ret === false) { - busy = false; - return; - } else if (typeof ret == 'object') { - selectedOpts = $.extend(selectedOpts, ret); - } - - title = selectedOpts.title || (obj.nodeName ? $(obj).attr('title') : obj.title) || ''; - - if (obj.nodeName && !selectedOpts.orig) { - selectedOpts.orig = $(obj).children("img:first").length ? $(obj).children("img:first") : $(obj); - } - - if (title === '' && selectedOpts.orig && selectedOpts.titleFromAlt) { - title = selectedOpts.orig.attr('alt'); - } - - href = selectedOpts.href || (obj.nodeName ? $(obj).attr('href') : obj.href) || null; - - if ((/^(?:javascript)/i).test(href) || href == '#') { - href = null; - } - - if (selectedOpts.type) { - type = selectedOpts.type; - - if (!href) { - href = selectedOpts.content; - } - - } else if (selectedOpts.content) { - type = 'html'; - - } else if (href) { - if (href.match(imgRegExp)) { - type = 'image'; - - } else if (href.match(swfRegExp)) { - type = 'swf'; - - } else if ($(obj).hasClass("iframe")) { - type = 'iframe'; - - } else if (href.indexOf("#") === 0) { - type = 'inline'; - - } else { - type = 'ajax'; - } - } - - if (!type) { - _error(); - return; - } - - if (type == 'inline') { - obj = href.substr(href.indexOf("#")); - type = $(obj).length > 0 ? 'inline' : 'ajax'; - } - - selectedOpts.type = type; - selectedOpts.href = href; - selectedOpts.title = title; - - if (selectedOpts.autoDimensions) { - if (selectedOpts.type == 'html' || selectedOpts.type == 'inline' || selectedOpts.type == 'ajax') { - selectedOpts.width = 'auto'; - selectedOpts.height = 'auto'; - } else { - selectedOpts.autoDimensions = false; - } - } - - if (selectedOpts.modal) { - selectedOpts.overlayShow = true; - selectedOpts.hideOnOverlayClick = false; - selectedOpts.hideOnContentClick = false; - selectedOpts.enableEscapeButton = false; - selectedOpts.showCloseButton = false; - } - - selectedOpts.padding = parseInt(selectedOpts.padding, 10); - selectedOpts.margin = parseInt(selectedOpts.margin, 10); - - tmp.css('padding', (selectedOpts.padding + selectedOpts.margin)); - - $('.fancybox-inline-tmp').unbind('fancybox-cancel').bind('fancybox-change', function() { - $(this).replaceWith(content.children()); - }); - - switch (type) { - case 'html' : - tmp.html( selectedOpts.content ); - _process_inline(); - break; - - case 'inline' : - if ( $(obj).parent().is('#fancybox-content') === true) { - busy = false; - return; - } - - $('
') - .hide() - .insertBefore( $(obj) ) - .bind('fancybox-cleanup', function() { - $(this).replaceWith(content.children()); - }).bind('fancybox-cancel', function() { - $(this).replaceWith(tmp.children()); - }); - - $(obj).appendTo(tmp); - - _process_inline(); - break; - - case 'image': - busy = false; - - $.fancybox.showActivity(); - - imgPreloader = new Image(); - - imgPreloader.onerror = function() { - _error(); - }; - - imgPreloader.onload = function() { - busy = true; - - imgPreloader.onerror = imgPreloader.onload = null; - - _process_image(); - }; - - imgPreloader.src = href; - break; - - case 'swf': - selectedOpts.scrolling = 'no'; - - str = ''; - emb = ''; - - $.each(selectedOpts.swf, function(name, val) { - str += ''; - emb += ' ' + name + '="' + val + '"'; - }); - - str += ''; - - tmp.html(str); - - _process_inline(); - break; - - case 'ajax': - busy = false; - - $.fancybox.showActivity(); - - selectedOpts.ajax.win = selectedOpts.ajax.success; - - ajaxLoader = $.ajax($.extend({}, selectedOpts.ajax, { - url : href, - data : selectedOpts.ajax.data || {}, - error : function(XMLHttpRequest, textStatus, errorThrown) { - if ( XMLHttpRequest.status > 0 ) { - _error(); - } - }, - success : function(data, textStatus, XMLHttpRequest) { - var o = typeof XMLHttpRequest == 'object' ? XMLHttpRequest : ajaxLoader; - if (o.status == 200) { - if ( typeof selectedOpts.ajax.win == 'function' ) { - ret = selectedOpts.ajax.win(href, data, textStatus, XMLHttpRequest); - - if (ret === false) { - loading.hide(); - return; - } else if (typeof ret == 'string' || typeof ret == 'object') { - data = ret; - } - } - - tmp.html( data ); - _process_inline(); - } - } - })); - - break; - - case 'iframe': - _show(); - break; - } - }, - - _process_inline = function() { - var - w = selectedOpts.width, - h = selectedOpts.height; - - if (w.toString().indexOf('%') > -1) { - w = parseInt( ($(window).width() - (selectedOpts.margin * 2)) * parseFloat(w) / 100, 10) + 'px'; - - } else { - w = w == 'auto' ? 'auto' : w + 'px'; - } - - if (h.toString().indexOf('%') > -1) { - h = parseInt( ($(window).height() - (selectedOpts.margin * 2)) * parseFloat(h) / 100, 10) + 'px'; - - } else { - h = h == 'auto' ? 'auto' : h + 'px'; - } - - tmp.wrapInner('
'); - - selectedOpts.width = tmp.width(); - selectedOpts.height = tmp.height(); - - _show(); - }, - - _process_image = function() { - selectedOpts.width = imgPreloader.width; - selectedOpts.height = imgPreloader.height; - - $("").attr({ - 'id' : 'fancybox-img', - 'src' : imgPreloader.src, - 'alt' : selectedOpts.title - }).appendTo( tmp ); - - _show(); - }, - - _show = function() { - var pos, equal; - - loading.hide(); - - if (wrap.is(":visible") && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { - $.event.trigger('fancybox-cancel'); - - busy = false; - return; - } - - busy = true; - - $(content.add( overlay )).unbind(); - - $(window).unbind("resize.fb scroll.fb"); - $(document).unbind('keydown.fb'); - - if (wrap.is(":visible") && currentOpts.titlePosition !== 'outside') { - wrap.css('height', wrap.height()); - } - - currentArray = selectedArray; - currentIndex = selectedIndex; - currentOpts = selectedOpts; - - if (currentOpts.overlayShow) { - overlay.css({ - 'background-color' : currentOpts.overlayColor, - 'opacity' : currentOpts.overlayOpacity, - 'cursor' : currentOpts.hideOnOverlayClick ? 'pointer' : 'auto', - 'height' : $(document).height() - }); - - if (!overlay.is(':visible')) { - if (isIE6) { - $('select:not(#fancybox-tmp select)').filter(function() { - return this.style.visibility !== 'hidden'; - }).css({'visibility' : 'hidden'}).one('fancybox-cleanup', function() { - this.style.visibility = 'inherit'; - }); - } - - overlay.show(); - } - } else { - overlay.hide(); - } - - final_pos = _get_zoom_to(); - - _process_title(); - - if (wrap.is(":visible")) { - $( close.add( nav_left ).add( nav_right ) ).hide(); - - pos = wrap.position(), - - start_pos = { - top : pos.top, - left : pos.left, - width : wrap.width(), - height : wrap.height() - }; - - equal = (start_pos.width == final_pos.width && start_pos.height == final_pos.height); - - content.fadeTo(currentOpts.changeFade, 0.3, function() { - var finish_resizing = function() { - content.html( tmp.contents() ).fadeTo(currentOpts.changeFade, 1, _finish); - }; - - $.event.trigger('fancybox-change'); - - content - .empty() - .removeAttr('filter') - .css({ - 'border-width' : currentOpts.padding, - 'width' : final_pos.width - currentOpts.padding * 2, - 'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 - }); - - if (equal) { - finish_resizing(); - - } else { - fx.prop = 0; - - $(fx).animate({prop: 1}, { - duration : currentOpts.changeSpeed, - easing : currentOpts.easingChange, - step : _draw, - complete : finish_resizing - }); - } - }); - - return; - } - - wrap.removeAttr("style"); - - content.css('border-width', currentOpts.padding); - - if (currentOpts.transitionIn == 'elastic') { - start_pos = _get_zoom_from(); - - content.html( tmp.contents() ); - - wrap.show(); - - if (currentOpts.opacity) { - final_pos.opacity = 0; - } - - fx.prop = 0; - - $(fx).animate({prop: 1}, { - duration : currentOpts.speedIn, - easing : currentOpts.easingIn, - step : _draw, - complete : _finish - }); - - return; - } - - if (currentOpts.titlePosition == 'inside' && titleHeight > 0) { - title.show(); - } - - content - .css({ - 'width' : final_pos.width - currentOpts.padding * 2, - 'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 - }) - .html( tmp.contents() ); - - wrap - .css(final_pos) - .fadeIn( currentOpts.transitionIn == 'none' ? 0 : currentOpts.speedIn, _finish ); - }, - - _format_title = function(title) { - if (title && title.length) { - if (currentOpts.titlePosition == 'float') { - return '
' + title + '
'; - } - - return '
' + title + '
'; - } - - return false; - }, - - _process_title = function() { - titleStr = currentOpts.title || ''; - titleHeight = 0; - - title - .empty() - .removeAttr('style') - .removeClass(); - - if (currentOpts.titleShow === false) { - title.hide(); - return; - } - - titleStr = $.isFunction(currentOpts.titleFormat) ? currentOpts.titleFormat(titleStr, currentArray, currentIndex, currentOpts) : _format_title(titleStr); - - if (!titleStr || titleStr === '') { - title.hide(); - return; - } - - title - .addClass('fancybox-title-' + currentOpts.titlePosition) - .html( titleStr ) - .appendTo( 'body' ) - .show(); - - switch (currentOpts.titlePosition) { - case 'inside': - title - .css({ - 'width' : final_pos.width - (currentOpts.padding * 2), - 'marginLeft' : currentOpts.padding, - 'marginRight' : currentOpts.padding - }); - - titleHeight = title.outerHeight(true); - - title.appendTo( outer ); - - final_pos.height += titleHeight; - break; - - case 'over': - title - .css({ - 'marginLeft' : currentOpts.padding, - 'width' : final_pos.width - (currentOpts.padding * 2), - 'bottom' : currentOpts.padding - }) - .appendTo( outer ); - break; - - case 'float': - title - .css('left', parseInt((title.width() - final_pos.width - 40)/ 2, 10) * -1) - .appendTo( wrap ); - break; - - default: - title - .css({ - 'width' : final_pos.width - (currentOpts.padding * 2), - 'paddingLeft' : currentOpts.padding, - 'paddingRight' : currentOpts.padding - }) - .appendTo( wrap ); - break; - } - - title.hide(); - }, - - _set_navigation = function() { - if (currentOpts.enableEscapeButton || currentOpts.enableKeyboardNav) { - $(document).bind('keydown.fb', function(e) { - if (e.keyCode == 27 && currentOpts.enableEscapeButton) { - e.preventDefault(); - $.fancybox.close(); - - } else if ((e.keyCode == 37 || e.keyCode == 39) && currentOpts.enableKeyboardNav && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') { - e.preventDefault(); - $.fancybox[ e.keyCode == 37 ? 'prev' : 'next'](); - } - }); - } - - if (!currentOpts.showNavArrows) { - nav_left.hide(); - nav_right.hide(); - return; - } - - if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex !== 0) { - nav_left.show(); - } - - if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex != (currentArray.length -1)) { - nav_right.show(); - } - }, - - _finish = function () { - if (!$.support.opacity) { - content.get(0).style.removeAttribute('filter'); - wrap.get(0).style.removeAttribute('filter'); - } - - if (selectedOpts.autoDimensions) { - content.css('height', 'auto'); - } - - wrap.css('height', 'auto'); - - if (titleStr && titleStr.length) { - title.show(); - } - - if (currentOpts.showCloseButton) { - close.show(); - } - - _set_navigation(); - - if (currentOpts.hideOnContentClick) { - content.bind('click', $.fancybox.close); - } - - if (currentOpts.hideOnOverlayClick) { - overlay.bind('click', $.fancybox.close); - } - - $(window).bind("resize.fb", $.fancybox.resize); - - if (currentOpts.centerOnScroll) { - $(window).bind("scroll.fb", $.fancybox.center); - } - - if (currentOpts.type == 'iframe') { - $('').appendTo(content); - } - - wrap.show(); - - busy = false; - - $.fancybox.center(); - - currentOpts.onComplete(currentArray, currentIndex, currentOpts); - - _preload_images(); - }, - - _preload_images = function() { - var href, - objNext; - - if ((currentArray.length -1) > currentIndex) { - href = currentArray[ currentIndex + 1 ].href; - - if (typeof href !== 'undefined' && href.match(imgRegExp)) { - objNext = new Image(); - objNext.src = href; - } - } - - if (currentIndex > 0) { - href = currentArray[ currentIndex - 1 ].href; - - if (typeof href !== 'undefined' && href.match(imgRegExp)) { - objNext = new Image(); - objNext.src = href; - } - } - }, - - _draw = function(pos) { - var dim = { - width : parseInt(start_pos.width + (final_pos.width - start_pos.width) * pos, 10), - height : parseInt(start_pos.height + (final_pos.height - start_pos.height) * pos, 10), - - top : parseInt(start_pos.top + (final_pos.top - start_pos.top) * pos, 10), - left : parseInt(start_pos.left + (final_pos.left - start_pos.left) * pos, 10) - }; - - if (typeof final_pos.opacity !== 'undefined') { - dim.opacity = pos < 0.5 ? 0.5 : pos; - } - - wrap.css(dim); - - content.css({ - 'width' : dim.width - currentOpts.padding * 2, - 'height' : dim.height - (titleHeight * pos) - currentOpts.padding * 2 - }); - }, - - _get_viewport = function() { - return [ - $(window).width() - (currentOpts.margin * 2), - $(window).height() - (currentOpts.margin * 2), - $(document).scrollLeft() + currentOpts.margin, - $(document).scrollTop() + currentOpts.margin - ]; - }, - - _get_zoom_to = function () { - var view = _get_viewport(), - to = {}, - resize = currentOpts.autoScale, - double_padding = currentOpts.padding * 2, - ratio; - - if (currentOpts.width.toString().indexOf('%') > -1) { - to.width = parseInt((view[0] * parseFloat(currentOpts.width)) / 100, 10); - } else { - to.width = currentOpts.width + double_padding; - } - - if (currentOpts.height.toString().indexOf('%') > -1) { - to.height = parseInt((view[1] * parseFloat(currentOpts.height)) / 100, 10); - } else { - to.height = currentOpts.height + double_padding; - } - - if (resize && (to.width > view[0] || to.height > view[1])) { - if (selectedOpts.type == 'image' || selectedOpts.type == 'swf') { - ratio = (currentOpts.width ) / (currentOpts.height ); - - if ((to.width ) > view[0]) { - to.width = view[0]; - to.height = parseInt(((to.width - double_padding) / ratio) + double_padding, 10); - } - - if ((to.height) > view[1]) { - to.height = view[1]; - to.width = parseInt(((to.height - double_padding) * ratio) + double_padding, 10); - } - - } else { - to.width = Math.min(to.width, view[0]); - to.height = Math.min(to.height, view[1]); - } - } - - to.top = parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - to.height - 40) * 0.5)), 10); - to.left = parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - to.width - 40) * 0.5)), 10); - - return to; - }, - - _get_obj_pos = function(obj) { - var pos = obj.offset(); - - pos.top += parseInt( obj.css('paddingTop'), 10 ) || 0; - pos.left += parseInt( obj.css('paddingLeft'), 10 ) || 0; - - pos.top += parseInt( obj.css('border-top-width'), 10 ) || 0; - pos.left += parseInt( obj.css('border-left-width'), 10 ) || 0; - - pos.width = obj.width(); - pos.height = obj.height(); - - return pos; - }, - - _get_zoom_from = function() { - var orig = selectedOpts.orig ? $(selectedOpts.orig) : false, - from = {}, - pos, - view; - - if (orig && orig.length) { - pos = _get_obj_pos(orig); - - from = { - width : pos.width + (currentOpts.padding * 2), - height : pos.height + (currentOpts.padding * 2), - top : pos.top - currentOpts.padding - 20, - left : pos.left - currentOpts.padding - 20 - }; - - } else { - view = _get_viewport(); - - from = { - width : currentOpts.padding * 2, - height : currentOpts.padding * 2, - top : parseInt(view[3] + view[1] * 0.5, 10), - left : parseInt(view[2] + view[0] * 0.5, 10) - }; - } - - return from; - }, - - _animate_loading = function() { - if (!loading.is(':visible')){ - clearInterval(loadingTimer); - return; - } - - $('div', loading).css('top', (loadingFrame * -40) + 'px'); - - loadingFrame = (loadingFrame + 1) % 12; - }; - - /* - * Public methods - */ - - $.fn.fancybox = function(options) { - if (!$(this).length) { - return this; - } - - $(this) - .data('fancybox', $.extend({}, options, ($.metadata ? $(this).metadata() : {}))) - .unbind('click.fb') - .bind('click.fb', function(e) { - e.preventDefault(); - - if (busy) { - return; - } - - busy = true; - - $(this).blur(); - - selectedArray = []; - selectedIndex = 0; - - var rel = $(this).attr('rel') || ''; - - if (!rel || rel == '' || rel === 'nofollow') { - selectedArray.push(this); - - } else { - selectedArray = $("a[rel=" + rel + "], area[rel=" + rel + "]"); - selectedIndex = selectedArray.index( this ); - } - - _start(); - - return; - }); - - return this; - }; - - $.fancybox = function(obj) { - var opts; - - if (busy) { - return; - } - - busy = true; - opts = typeof arguments[1] !== 'undefined' ? arguments[1] : {}; - - selectedArray = []; - selectedIndex = parseInt(opts.index, 10) || 0; - - if ($.isArray(obj)) { - for (var i = 0, j = obj.length; i < j; i++) { - if (typeof obj[i] == 'object') { - $(obj[i]).data('fancybox', $.extend({}, opts, obj[i])); - } else { - obj[i] = $({}).data('fancybox', $.extend({content : obj[i]}, opts)); - } - } - - selectedArray = jQuery.merge(selectedArray, obj); - - } else { - if (typeof obj == 'object') { - $(obj).data('fancybox', $.extend({}, opts, obj)); - } else { - obj = $({}).data('fancybox', $.extend({content : obj}, opts)); - } - - selectedArray.push(obj); - } - - if (selectedIndex > selectedArray.length || selectedIndex < 0) { - selectedIndex = 0; - } - - _start(); - }; - - $.fancybox.showActivity = function() { - clearInterval(loadingTimer); - - loading.show(); - loadingTimer = setInterval(_animate_loading, 66); - }; - - $.fancybox.hideActivity = function() { - loading.hide(); - }; - - $.fancybox.next = function() { - return $.fancybox.pos( currentIndex + 1); - }; - - $.fancybox.prev = function() { - return $.fancybox.pos( currentIndex - 1); - }; - - $.fancybox.pos = function(pos) { - if (busy) { - return; - } - - pos = parseInt(pos); - - selectedArray = currentArray; - - if (pos > -1 && pos < currentArray.length) { - selectedIndex = pos; - _start(); - - } else if (currentOpts.cyclic && currentArray.length > 1) { - selectedIndex = pos >= currentArray.length ? 0 : currentArray.length - 1; - _start(); - } - - return; - }; - - $.fancybox.cancel = function() { - if (busy) { - return; - } - - busy = true; - - $.event.trigger('fancybox-cancel'); - - _abort(); - - selectedOpts.onCancel(selectedArray, selectedIndex, selectedOpts); - - busy = false; - }; - - // Note: within an iframe use - parent.$.fancybox.close(); - $.fancybox.close = function() { - if (busy || wrap.is(':hidden')) { - return; - } - - busy = true; - - if (currentOpts && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { - busy = false; - return; - } - - _abort(); - - $(close.add( nav_left ).add( nav_right )).hide(); - - $(content.add( overlay )).unbind(); - - $(window).unbind("resize.fb scroll.fb"); - $(document).unbind('keydown.fb'); - - content.find('iframe').attr('src', isIE6 && /^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank'); - - if (currentOpts.titlePosition !== 'inside') { - title.empty(); - } - - wrap.stop(); - - function _cleanup() { - overlay.fadeOut('fast'); - - title.empty().hide(); - wrap.hide(); - - $.event.trigger('fancybox-cleanup'); - - content.empty(); - - currentOpts.onClosed(currentArray, currentIndex, currentOpts); - - currentArray = selectedOpts = []; - currentIndex = selectedIndex = 0; - currentOpts = selectedOpts = {}; - - busy = false; - } - - if (currentOpts.transitionOut == 'elastic') { - start_pos = _get_zoom_from(); - - var pos = wrap.position(); - - final_pos = { - top : pos.top , - left : pos.left, - width : wrap.width(), - height : wrap.height() - }; - - if (currentOpts.opacity) { - final_pos.opacity = 1; - } - - title.empty().hide(); - - fx.prop = 1; - - $(fx).animate({ prop: 0 }, { - duration : currentOpts.speedOut, - easing : currentOpts.easingOut, - step : _draw, - complete : _cleanup - }); - - } else { - wrap.fadeOut( currentOpts.transitionOut == 'none' ? 0 : currentOpts.speedOut, _cleanup); - } - }; - - $.fancybox.resize = function() { - if (overlay.is(':visible')) { - overlay.css('height', $(document).height()); - } - - $.fancybox.center(true); - }; - - $.fancybox.center = function() { - var view, align; - - if (busy) { - return; - } - - align = arguments[0] === true ? 1 : 0; - view = _get_viewport(); - - if (!align && (wrap.width() > view[0] || wrap.height() > view[1])) { - return; - } - - wrap - .stop() - .animate({ - 'top' : parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - content.height() - 40) * 0.5) - currentOpts.padding)), - 'left' : parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - content.width() - 40) * 0.5) - currentOpts.padding)) - }, typeof arguments[0] == 'number' ? arguments[0] : 200); - }; - - $.fancybox.init = function() { - if ($("#fancybox-wrap").length) { - return; - } - - $('body').append( - tmp = $('
'), - loading = $('
'), - overlay = $('
'), - wrap = $('
') - ); - - outer = $('
') - .append('
') - .appendTo( wrap ); - - outer.append( - content = $('
'), - close = $(''), - title = $('
'), - - nav_left = $(''), - nav_right = $('') - ); - - close.click($.fancybox.close); - loading.click($.fancybox.cancel); - - nav_left.click(function(e) { - e.preventDefault(); - $.fancybox.prev(); - }); - - nav_right.click(function(e) { - e.preventDefault(); - $.fancybox.next(); - }); - - if ($.fn.mousewheel) { - wrap.bind('mousewheel.fb', function(e, delta) { - if (busy) { - e.preventDefault(); - - } else if ($(e.target).get(0).clientHeight == 0 || $(e.target).get(0).scrollHeight === $(e.target).get(0).clientHeight) { - e.preventDefault(); - $.fancybox[ delta > 0 ? 'prev' : 'next'](); - } - }); - } - - if (!$.support.opacity) { - wrap.addClass('fancybox-ie'); - } - - if (isIE6) { - loading.addClass('fancybox-ie6'); - wrap.addClass('fancybox-ie6'); - - $('').prependTo(outer); - } - }; - - $.fn.fancybox.defaults = { - padding : 10, - margin : 40, - opacity : false, - modal : false, - cyclic : false, - scrolling : 'auto', // 'auto', 'yes' or 'no' - - width : 560, - height : 340, - - autoScale : true, - autoDimensions : true, - centerOnScroll : false, - - ajax : {}, - swf : { wmode: 'transparent' }, - - hideOnOverlayClick : true, - hideOnContentClick : false, - - overlayShow : true, - overlayOpacity : 0.7, - overlayColor : '#777', - - titleShow : true, - titlePosition : 'float', // 'float', 'outside', 'inside' or 'over' - titleFormat : null, - titleFromAlt : false, - - transitionIn : 'fade', // 'elastic', 'fade' or 'none' - transitionOut : 'fade', // 'elastic', 'fade' or 'none' - - speedIn : 300, - speedOut : 300, - - changeSpeed : 300, - changeFade : 'fast', - - easingIn : 'swing', - easingOut : 'swing', - - showCloseButton : true, - showNavArrows : true, - enableEscapeButton : true, - enableKeyboardNav : true, - - onStart : function(){}, - onCancel : function(){}, - onComplete : function(){}, - onCleanup : function(){}, - onClosed : function(){}, - onError : function(){} - }; - - $(document).ready(function() { - $.fancybox.init(); - }); - +/* + * FancyBox - jQuery Plugin + * Simple and fancy lightbox alternative + * + * Examples and documentation at: http://fancybox.net + * + * Copyright (c) 2008 - 2010 Janis Skarnelis + * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. + * + * Version: 1.3.4 (11/11/2010) + * Requires: jQuery v1.3+ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +;(function($) { + var tmp, loading, overlay, wrap, outer, content, close, title, nav_left, nav_right, + + selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [], + + ajaxLoader = null, imgPreloader = new Image(), imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i, swfRegExp = /[^\.]\.(swf)\s*$/i, + + loadingTimer, loadingFrame = 1, + + titleHeight = 0, titleStr = '', start_pos, final_pos, busy = false, fx = $.extend($('
')[0], { prop: 0 }), + + isIE6 = $.browser.msie && $.browser.version < 7 && !window.XMLHttpRequest, + + /* + * Private methods + */ + + _abort = function() { + loading.hide(); + + imgPreloader.onerror = imgPreloader.onload = null; + + if (ajaxLoader) { + ajaxLoader.abort(); + } + + tmp.empty(); + }, + + _error = function() { + if (false === selectedOpts.onError(selectedArray, selectedIndex, selectedOpts)) { + loading.hide(); + busy = false; + return; + } + + selectedOpts.titleShow = false; + + selectedOpts.width = 'auto'; + selectedOpts.height = 'auto'; + + tmp.html( '

The requested content cannot be loaded.
Please try again later.

' ); + + _process_inline(); + }, + + _start = function() { + var obj = selectedArray[ selectedIndex ], + href, + type, + title, + str, + emb, + ret; + + _abort(); + + selectedOpts = $.extend({}, $.fn.fancybox.defaults, (typeof $(obj).data('fancybox') == 'undefined' ? selectedOpts : $(obj).data('fancybox'))); + + ret = selectedOpts.onStart(selectedArray, selectedIndex, selectedOpts); + + if (ret === false) { + busy = false; + return; + } else if (typeof ret == 'object') { + selectedOpts = $.extend(selectedOpts, ret); + } + + title = selectedOpts.title || (obj.nodeName ? $(obj).attr('title') : obj.title) || ''; + + if (obj.nodeName && !selectedOpts.orig) { + selectedOpts.orig = $(obj).children("img:first").length ? $(obj).children("img:first") : $(obj); + } + + if (title === '' && selectedOpts.orig && selectedOpts.titleFromAlt) { + title = selectedOpts.orig.attr('alt'); + } + + href = selectedOpts.href || (obj.nodeName ? $(obj).attr('href') : obj.href) || null; + + if ((/^(?:javascript)/i).test(href) || href == '#') { + href = null; + } + + if (selectedOpts.type) { + type = selectedOpts.type; + + if (!href) { + href = selectedOpts.content; + } + + } else if (selectedOpts.content) { + type = 'html'; + + } else if (href) { + if (href.match(imgRegExp)) { + type = 'image'; + + } else if (href.match(swfRegExp)) { + type = 'swf'; + + } else if ($(obj).hasClass("iframe")) { + type = 'iframe'; + + } else if (href.indexOf("#") === 0) { + type = 'inline'; + + } else { + type = 'ajax'; + } + } + + if (!type) { + _error(); + return; + } + + if (type == 'inline') { + obj = href.substr(href.indexOf("#")); + type = $(obj).length > 0 ? 'inline' : 'ajax'; + } + + selectedOpts.type = type; + selectedOpts.href = href; + selectedOpts.title = title; + + if (selectedOpts.autoDimensions) { + if (selectedOpts.type == 'html' || selectedOpts.type == 'inline' || selectedOpts.type == 'ajax') { + selectedOpts.width = 'auto'; + selectedOpts.height = 'auto'; + } else { + selectedOpts.autoDimensions = false; + } + } + + if (selectedOpts.modal) { + selectedOpts.overlayShow = true; + selectedOpts.hideOnOverlayClick = false; + selectedOpts.hideOnContentClick = false; + selectedOpts.enableEscapeButton = false; + selectedOpts.showCloseButton = false; + } + + selectedOpts.padding = parseInt(selectedOpts.padding, 10); + selectedOpts.margin = parseInt(selectedOpts.margin, 10); + + tmp.css('padding', (selectedOpts.padding + selectedOpts.margin)); + + $('.fancybox-inline-tmp').unbind('fancybox-cancel').bind('fancybox-change', function() { + $(this).replaceWith(content.children()); + }); + + switch (type) { + case 'html' : + tmp.html( selectedOpts.content ); + _process_inline(); + break; + + case 'inline' : + if ( $(obj).parent().is('#fancybox-content') === true) { + busy = false; + return; + } + + $('
') + .hide() + .insertBefore( $(obj) ) + .bind('fancybox-cleanup', function() { + $(this).replaceWith(content.children()); + }).bind('fancybox-cancel', function() { + $(this).replaceWith(tmp.children()); + }); + + $(obj).appendTo(tmp); + + _process_inline(); + break; + + case 'image': + busy = false; + + $.fancybox.showActivity(); + + imgPreloader = new Image(); + + imgPreloader.onerror = function() { + _error(); + }; + + imgPreloader.onload = function() { + busy = true; + + imgPreloader.onerror = imgPreloader.onload = null; + + _process_image(); + }; + + imgPreloader.src = href; + break; + + case 'swf': + selectedOpts.scrolling = 'no'; + + str = ''; + emb = ''; + + $.each(selectedOpts.swf, function(name, val) { + str += ''; + emb += ' ' + name + '="' + val + '"'; + }); + + str += ''; + + tmp.html(str); + + _process_inline(); + break; + + case 'ajax': + busy = false; + + $.fancybox.showActivity(); + + selectedOpts.ajax.win = selectedOpts.ajax.success; + + ajaxLoader = $.ajax($.extend({}, selectedOpts.ajax, { + url : href, + data : selectedOpts.ajax.data || {}, + error : function(XMLHttpRequest, textStatus, errorThrown) { + if ( XMLHttpRequest.status > 0 ) { + _error(); + } + }, + success : function(data, textStatus, XMLHttpRequest) { + var o = typeof XMLHttpRequest == 'object' ? XMLHttpRequest : ajaxLoader; + if (o.status == 200) { + if ( typeof selectedOpts.ajax.win == 'function' ) { + ret = selectedOpts.ajax.win(href, data, textStatus, XMLHttpRequest); + + if (ret === false) { + loading.hide(); + return; + } else if (typeof ret == 'string' || typeof ret == 'object') { + data = ret; + } + } + + tmp.html( data ); + _process_inline(); + } + } + })); + + break; + + case 'iframe': + _show(); + break; + } + }, + + _process_inline = function() { + var + w = selectedOpts.width, + h = selectedOpts.height; + + if (w.toString().indexOf('%') > -1) { + w = parseInt( ($(window).width() - (selectedOpts.margin * 2)) * parseFloat(w) / 100, 10) + 'px'; + + } else { + w = w == 'auto' ? 'auto' : w + 'px'; + } + + if (h.toString().indexOf('%') > -1) { + h = parseInt( ($(window).height() - (selectedOpts.margin * 2)) * parseFloat(h) / 100, 10) + 'px'; + + } else { + h = h == 'auto' ? 'auto' : h + 'px'; + } + + tmp.wrapInner('
'); + + selectedOpts.width = tmp.width(); + selectedOpts.height = tmp.height(); + + _show(); + }, + + _process_image = function() { + selectedOpts.width = imgPreloader.width; + selectedOpts.height = imgPreloader.height; + + $("").attr({ + 'id' : 'fancybox-img', + 'src' : imgPreloader.src, + 'alt' : selectedOpts.title + }).appendTo( tmp ); + + _show(); + }, + + _show = function() { + var pos, equal; + + loading.hide(); + + if (wrap.is(":visible") && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { + $.event.trigger('fancybox-cancel'); + + busy = false; + return; + } + + busy = true; + + $(content.add( overlay )).unbind(); + + $(window).unbind("resize.fb scroll.fb"); + $(document).unbind('keydown.fb'); + + if (wrap.is(":visible") && currentOpts.titlePosition !== 'outside') { + wrap.css('height', wrap.height()); + } + + currentArray = selectedArray; + currentIndex = selectedIndex; + currentOpts = selectedOpts; + + if (currentOpts.overlayShow) { + overlay.css({ + 'background-color' : currentOpts.overlayColor, + 'opacity' : currentOpts.overlayOpacity, + 'cursor' : currentOpts.hideOnOverlayClick ? 'pointer' : 'auto', + 'height' : $(document).height() + }); + + if (!overlay.is(':visible')) { + if (isIE6) { + $('select:not(#fancybox-tmp select)').filter(function() { + return this.style.visibility !== 'hidden'; + }).css({'visibility' : 'hidden'}).one('fancybox-cleanup', function() { + this.style.visibility = 'inherit'; + }); + } + + overlay.show(); + } + } else { + overlay.hide(); + } + + final_pos = _get_zoom_to(); + + _process_title(); + + if (wrap.is(":visible")) { + $( close.add( nav_left ).add( nav_right ) ).hide(); + + pos = wrap.position(), + + start_pos = { + top : pos.top, + left : pos.left, + width : wrap.width(), + height : wrap.height() + }; + + equal = (start_pos.width == final_pos.width && start_pos.height == final_pos.height); + + content.fadeTo(currentOpts.changeFade, 0.3, function() { + var finish_resizing = function() { + content.html( tmp.contents() ).fadeTo(currentOpts.changeFade, 1, _finish); + }; + + $.event.trigger('fancybox-change'); + + content + .empty() + .removeAttr('filter') + .css({ + 'border-width' : currentOpts.padding, + 'width' : final_pos.width - currentOpts.padding * 2, + 'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 + }); + + if (equal) { + finish_resizing(); + + } else { + fx.prop = 0; + + $(fx).animate({prop: 1}, { + duration : currentOpts.changeSpeed, + easing : currentOpts.easingChange, + step : _draw, + complete : finish_resizing + }); + } + }); + + return; + } + + wrap.removeAttr("style"); + + content.css('border-width', currentOpts.padding); + + if (currentOpts.transitionIn == 'elastic') { + start_pos = _get_zoom_from(); + + content.html( tmp.contents() ); + + wrap.show(); + + if (currentOpts.opacity) { + final_pos.opacity = 0; + } + + fx.prop = 0; + + $(fx).animate({prop: 1}, { + duration : currentOpts.speedIn, + easing : currentOpts.easingIn, + step : _draw, + complete : _finish + }); + + return; + } + + if (currentOpts.titlePosition == 'inside' && titleHeight > 0) { + title.show(); + } + + content + .css({ + 'width' : final_pos.width - currentOpts.padding * 2, + 'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 + }) + .html( tmp.contents() ); + + wrap + .css(final_pos) + .fadeIn( currentOpts.transitionIn == 'none' ? 0 : currentOpts.speedIn, _finish ); + }, + + _format_title = function(title) { + if (title && title.length) { + if (currentOpts.titlePosition == 'float') { + return '
' + title + '
'; + } + + return '
' + title + '
'; + } + + return false; + }, + + _process_title = function() { + titleStr = currentOpts.title || ''; + titleHeight = 0; + + title + .empty() + .removeAttr('style') + .removeClass(); + + if (currentOpts.titleShow === false) { + title.hide(); + return; + } + + titleStr = $.isFunction(currentOpts.titleFormat) ? currentOpts.titleFormat(titleStr, currentArray, currentIndex, currentOpts) : _format_title(titleStr); + + if (!titleStr || titleStr === '') { + title.hide(); + return; + } + + title + .addClass('fancybox-title-' + currentOpts.titlePosition) + .html( titleStr ) + .appendTo( 'body' ) + .show(); + + switch (currentOpts.titlePosition) { + case 'inside': + title + .css({ + 'width' : final_pos.width - (currentOpts.padding * 2), + 'marginLeft' : currentOpts.padding, + 'marginRight' : currentOpts.padding + }); + + titleHeight = title.outerHeight(true); + + title.appendTo( outer ); + + final_pos.height += titleHeight; + break; + + case 'over': + title + .css({ + 'marginLeft' : currentOpts.padding, + 'width' : final_pos.width - (currentOpts.padding * 2), + 'bottom' : currentOpts.padding + }) + .appendTo( outer ); + break; + + case 'float': + title + .css('left', parseInt((title.width() - final_pos.width - 40)/ 2, 10) * -1) + .appendTo( wrap ); + break; + + default: + title + .css({ + 'width' : final_pos.width - (currentOpts.padding * 2), + 'paddingLeft' : currentOpts.padding, + 'paddingRight' : currentOpts.padding + }) + .appendTo( wrap ); + break; + } + + title.hide(); + }, + + _set_navigation = function() { + if (currentOpts.enableEscapeButton || currentOpts.enableKeyboardNav) { + $(document).bind('keydown.fb', function(e) { + if (e.keyCode == 27 && currentOpts.enableEscapeButton) { + e.preventDefault(); + $.fancybox.close(); + + } else if ((e.keyCode == 37 || e.keyCode == 39) && currentOpts.enableKeyboardNav && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') { + e.preventDefault(); + $.fancybox[ e.keyCode == 37 ? 'prev' : 'next'](); + } + }); + } + + if (!currentOpts.showNavArrows) { + nav_left.hide(); + nav_right.hide(); + return; + } + + if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex !== 0) { + nav_left.show(); + } + + if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex != (currentArray.length -1)) { + nav_right.show(); + } + }, + + _finish = function () { + if (!$.support.opacity) { + content.get(0).style.removeAttribute('filter'); + wrap.get(0).style.removeAttribute('filter'); + } + + if (selectedOpts.autoDimensions) { + content.css('height', 'auto'); + } + + wrap.css('height', 'auto'); + + if (titleStr && titleStr.length) { + title.show(); + } + + if (currentOpts.showCloseButton) { + close.show(); + } + + _set_navigation(); + + if (currentOpts.hideOnContentClick) { + content.bind('click', $.fancybox.close); + } + + if (currentOpts.hideOnOverlayClick) { + overlay.bind('click', $.fancybox.close); + } + + $(window).bind("resize.fb", $.fancybox.resize); + + if (currentOpts.centerOnScroll) { + $(window).bind("scroll.fb", $.fancybox.center); + } + + if (currentOpts.type == 'iframe') { + $('').appendTo(content); + } + + wrap.show(); + + busy = false; + + $.fancybox.center(); + + currentOpts.onComplete(currentArray, currentIndex, currentOpts); + + _preload_images(); + }, + + _preload_images = function() { + var href, + objNext; + + if ((currentArray.length -1) > currentIndex) { + href = currentArray[ currentIndex + 1 ].href; + + if (typeof href !== 'undefined' && href.match(imgRegExp)) { + objNext = new Image(); + objNext.src = href; + } + } + + if (currentIndex > 0) { + href = currentArray[ currentIndex - 1 ].href; + + if (typeof href !== 'undefined' && href.match(imgRegExp)) { + objNext = new Image(); + objNext.src = href; + } + } + }, + + _draw = function(pos) { + var dim = { + width : parseInt(start_pos.width + (final_pos.width - start_pos.width) * pos, 10), + height : parseInt(start_pos.height + (final_pos.height - start_pos.height) * pos, 10), + + top : parseInt(start_pos.top + (final_pos.top - start_pos.top) * pos, 10), + left : parseInt(start_pos.left + (final_pos.left - start_pos.left) * pos, 10) + }; + + if (typeof final_pos.opacity !== 'undefined') { + dim.opacity = pos < 0.5 ? 0.5 : pos; + } + + wrap.css(dim); + + content.css({ + 'width' : dim.width - currentOpts.padding * 2, + 'height' : dim.height - (titleHeight * pos) - currentOpts.padding * 2 + }); + }, + + _get_viewport = function() { + return [ + $(window).width() - (currentOpts.margin * 2), + $(window).height() - (currentOpts.margin * 2), + $(document).scrollLeft() + currentOpts.margin, + $(document).scrollTop() + currentOpts.margin + ]; + }, + + _get_zoom_to = function () { + var view = _get_viewport(), + to = {}, + resize = currentOpts.autoScale, + double_padding = currentOpts.padding * 2, + ratio; + + if (currentOpts.width.toString().indexOf('%') > -1) { + to.width = parseInt((view[0] * parseFloat(currentOpts.width)) / 100, 10); + } else { + to.width = currentOpts.width + double_padding; + } + + if (currentOpts.height.toString().indexOf('%') > -1) { + to.height = parseInt((view[1] * parseFloat(currentOpts.height)) / 100, 10); + } else { + to.height = currentOpts.height + double_padding; + } + + if (resize && (to.width > view[0] || to.height > view[1])) { + if (selectedOpts.type == 'image' || selectedOpts.type == 'swf') { + ratio = (currentOpts.width ) / (currentOpts.height ); + + if ((to.width ) > view[0]) { + to.width = view[0]; + to.height = parseInt(((to.width - double_padding) / ratio) + double_padding, 10); + } + + if ((to.height) > view[1]) { + to.height = view[1]; + to.width = parseInt(((to.height - double_padding) * ratio) + double_padding, 10); + } + + } else { + to.width = Math.min(to.width, view[0]); + to.height = Math.min(to.height, view[1]); + } + } + + to.top = parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - to.height - 40) * 0.5)), 10); + to.left = parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - to.width - 40) * 0.5)), 10); + + return to; + }, + + _get_obj_pos = function(obj) { + var pos = obj.offset(); + + pos.top += parseInt( obj.css('paddingTop'), 10 ) || 0; + pos.left += parseInt( obj.css('paddingLeft'), 10 ) || 0; + + pos.top += parseInt( obj.css('border-top-width'), 10 ) || 0; + pos.left += parseInt( obj.css('border-left-width'), 10 ) || 0; + + pos.width = obj.width(); + pos.height = obj.height(); + + return pos; + }, + + _get_zoom_from = function() { + var orig = selectedOpts.orig ? $(selectedOpts.orig) : false, + from = {}, + pos, + view; + + if (orig && orig.length) { + pos = _get_obj_pos(orig); + + from = { + width : pos.width + (currentOpts.padding * 2), + height : pos.height + (currentOpts.padding * 2), + top : pos.top - currentOpts.padding - 20, + left : pos.left - currentOpts.padding - 20 + }; + + } else { + view = _get_viewport(); + + from = { + width : currentOpts.padding * 2, + height : currentOpts.padding * 2, + top : parseInt(view[3] + view[1] * 0.5, 10), + left : parseInt(view[2] + view[0] * 0.5, 10) + }; + } + + return from; + }, + + _animate_loading = function() { + if (!loading.is(':visible')){ + clearInterval(loadingTimer); + return; + } + + $('div', loading).css('top', (loadingFrame * -40) + 'px'); + + loadingFrame = (loadingFrame + 1) % 12; + }; + + /* + * Public methods + */ + + $.fn.fancybox = function(options) { + if (!$(this).length) { + return this; + } + + $(this) + .data('fancybox', $.extend({}, options, ($.metadata ? $(this).metadata() : {}))) + .unbind('click.fb') + .bind('click.fb', function(e) { + e.preventDefault(); + + if (busy) { + return; + } + + busy = true; + + $(this).blur(); + + selectedArray = []; + selectedIndex = 0; + + var rel = $(this).attr('rel') || ''; + + if (!rel || rel == '' || rel === 'nofollow') { + selectedArray.push(this); + + } else { + selectedArray = $("a[rel=" + rel + "], area[rel=" + rel + "]"); + selectedIndex = selectedArray.index( this ); + } + + _start(); + + return; + }); + + return this; + }; + + $.fancybox = function(obj) { + var opts; + + if (busy) { + return; + } + + busy = true; + opts = typeof arguments[1] !== 'undefined' ? arguments[1] : {}; + + selectedArray = []; + selectedIndex = parseInt(opts.index, 10) || 0; + + if ($.isArray(obj)) { + for (var i = 0, j = obj.length; i < j; i++) { + if (typeof obj[i] == 'object') { + $(obj[i]).data('fancybox', $.extend({}, opts, obj[i])); + } else { + obj[i] = $({}).data('fancybox', $.extend({content : obj[i]}, opts)); + } + } + + selectedArray = jQuery.merge(selectedArray, obj); + + } else { + if (typeof obj == 'object') { + $(obj).data('fancybox', $.extend({}, opts, obj)); + } else { + obj = $({}).data('fancybox', $.extend({content : obj}, opts)); + } + + selectedArray.push(obj); + } + + if (selectedIndex > selectedArray.length || selectedIndex < 0) { + selectedIndex = 0; + } + + _start(); + }; + + $.fancybox.showActivity = function() { + clearInterval(loadingTimer); + + loading.show(); + loadingTimer = setInterval(_animate_loading, 66); + }; + + $.fancybox.hideActivity = function() { + loading.hide(); + }; + + $.fancybox.next = function() { + return $.fancybox.pos( currentIndex + 1); + }; + + $.fancybox.prev = function() { + return $.fancybox.pos( currentIndex - 1); + }; + + $.fancybox.pos = function(pos) { + if (busy) { + return; + } + + pos = parseInt(pos); + + selectedArray = currentArray; + + if (pos > -1 && pos < currentArray.length) { + selectedIndex = pos; + _start(); + + } else if (currentOpts.cyclic && currentArray.length > 1) { + selectedIndex = pos >= currentArray.length ? 0 : currentArray.length - 1; + _start(); + } + + return; + }; + + $.fancybox.cancel = function() { + if (busy) { + return; + } + + busy = true; + + $.event.trigger('fancybox-cancel'); + + _abort(); + + selectedOpts.onCancel(selectedArray, selectedIndex, selectedOpts); + + busy = false; + }; + + // Note: within an iframe use - parent.$.fancybox.close(); + $.fancybox.close = function() { + if (busy || wrap.is(':hidden')) { + return; + } + + busy = true; + + if (currentOpts && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { + busy = false; + return; + } + + _abort(); + + $(close.add( nav_left ).add( nav_right )).hide(); + + $(content.add( overlay )).unbind(); + + $(window).unbind("resize.fb scroll.fb"); + $(document).unbind('keydown.fb'); + + content.find('iframe').attr('src', isIE6 && /^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank'); + + if (currentOpts.titlePosition !== 'inside') { + title.empty(); + } + + wrap.stop(); + + function _cleanup() { + overlay.fadeOut('fast'); + + title.empty().hide(); + wrap.hide(); + + $.event.trigger('fancybox-cleanup'); + + content.empty(); + + currentOpts.onClosed(currentArray, currentIndex, currentOpts); + + currentArray = selectedOpts = []; + currentIndex = selectedIndex = 0; + currentOpts = selectedOpts = {}; + + busy = false; + } + + if (currentOpts.transitionOut == 'elastic') { + start_pos = _get_zoom_from(); + + var pos = wrap.position(); + + final_pos = { + top : pos.top , + left : pos.left, + width : wrap.width(), + height : wrap.height() + }; + + if (currentOpts.opacity) { + final_pos.opacity = 1; + } + + title.empty().hide(); + + fx.prop = 1; + + $(fx).animate({ prop: 0 }, { + duration : currentOpts.speedOut, + easing : currentOpts.easingOut, + step : _draw, + complete : _cleanup + }); + + } else { + wrap.fadeOut( currentOpts.transitionOut == 'none' ? 0 : currentOpts.speedOut, _cleanup); + } + }; + + $.fancybox.resize = function() { + if (overlay.is(':visible')) { + overlay.css('height', $(document).height()); + } + + $.fancybox.center(true); + }; + + $.fancybox.center = function() { + var view, align; + + if (busy) { + return; + } + + align = arguments[0] === true ? 1 : 0; + view = _get_viewport(); + + if (!align && (wrap.width() > view[0] || wrap.height() > view[1])) { + return; + } + + wrap + .stop() + .animate({ + 'top' : parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - content.height() - 40) * 0.5) - currentOpts.padding)), + 'left' : parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - content.width() - 40) * 0.5) - currentOpts.padding)) + }, typeof arguments[0] == 'number' ? arguments[0] : 200); + }; + + $.fancybox.init = function() { + if ($("#fancybox-wrap").length) { + return; + } + + $('body').append( + tmp = $('
'), + loading = $('
'), + overlay = $('
'), + wrap = $('
') + ); + + outer = $('
') + .append('
') + .appendTo( wrap ); + + outer.append( + content = $('
'), + close = $(''), + title = $('
'), + + nav_left = $(''), + nav_right = $('') + ); + + close.click($.fancybox.close); + loading.click($.fancybox.cancel); + + nav_left.click(function(e) { + e.preventDefault(); + $.fancybox.prev(); + }); + + nav_right.click(function(e) { + e.preventDefault(); + $.fancybox.next(); + }); + + if ($.fn.mousewheel) { + wrap.bind('mousewheel.fb', function(e, delta) { + if (busy) { + e.preventDefault(); + + } else if ($(e.target).get(0).clientHeight == 0 || $(e.target).get(0).scrollHeight === $(e.target).get(0).clientHeight) { + e.preventDefault(); + $.fancybox[ delta > 0 ? 'prev' : 'next'](); + } + }); + } + + if (!$.support.opacity) { + wrap.addClass('fancybox-ie'); + } + + if (isIE6) { + loading.addClass('fancybox-ie6'); + wrap.addClass('fancybox-ie6'); + + $('').prependTo(outer); + } + }; + + $.fn.fancybox.defaults = { + padding : 10, + margin : 40, + opacity : false, + modal : false, + cyclic : false, + scrolling : 'auto', // 'auto', 'yes' or 'no' + + width : 560, + height : 340, + + autoScale : true, + autoDimensions : true, + centerOnScroll : false, + + ajax : {}, + swf : { wmode: 'transparent' }, + + hideOnOverlayClick : true, + hideOnContentClick : false, + + overlayShow : true, + overlayOpacity : 0.7, + overlayColor : '#777', + + titleShow : true, + titlePosition : 'float', // 'float', 'outside', 'inside' or 'over' + titleFormat : null, + titleFromAlt : false, + + transitionIn : 'fade', // 'elastic', 'fade' or 'none' + transitionOut : 'fade', // 'elastic', 'fade' or 'none' + + speedIn : 300, + speedOut : 300, + + changeSpeed : 300, + changeFade : 'fast', + + easingIn : 'swing', + easingOut : 'swing', + + showCloseButton : true, + showNavArrows : true, + enableEscapeButton : true, + enableKeyboardNav : true, + + onStart : function(){}, + onCancel : function(){}, + onComplete : function(){}, + onCleanup : function(){}, + onClosed : function(){}, + onError : function(){} + }; + + $(document).ready(function() { + $.fancybox.init(); + }); + })(jQuery); \ No newline at end of file diff --git a/data/interfaces/default/searchresults.html b/data/interfaces/default/searchresults.html index b6083c51..752e0424 100644 --- a/data/interfaces/default/searchresults.html +++ b/data/interfaces/default/searchresults.html @@ -11,36 +11,49 @@ %if type == 'album': Album Name + Artist Name + Format + Tracks + Date + Score + %else: + Artist Name + Score %endif - Artist Name - Score - + %if searchresults: - %for result in searchresults: - <% - if result['score'] == 100: - grade = 'A' - else: - grade = 'Z' - %> + %for result in searchresults: + <% + if result['score'] == 100: + grade = 'A' + else: + grade = 'Z' + + if type == 'album': + albuminfo = 'Type: ' + result['rgtype'] + ', Country: ' + result['country'] + caa_group_url = "http://coverartarchive.org/release-group/%s/front-250.jpg" %result['rgid'] + %> %if type == 'album': -
+
%else:
%endif %if type == 'album': - ${result['title']} - %endif - ${result['uniquename']} -
${result['score']}
- %if type == 'album': - Add this album - %else: - View on MusicBrainz + ${result['title']} + ${result['uniquename']} + ${result['formats']} + ${result['tracks']} + ${result['date']} +
${result['score']}
+ + %else: + ${result['uniquename']} +
${result['score']}
+ %endif %endfor @@ -75,7 +88,7 @@ { "bDestroy": true, "aoColumnDefs": [ - { 'bSortable': false, 'aTargets': [ 0,3 ] } + { 'bSortable': false, 'aTargets': [ 0 ] } ], "oLanguage": { "sLengthMenu":"Show _MENU_ results per page", diff --git a/headphones/__init__.py b/headphones/__init__.py index a84dc54a..e80ffb7b 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -170,10 +170,6 @@ NZBSORG = False NZBSORG_UID = None NZBSORG_HASH = None -NZBSRUS = False -NZBSRUS_UID = None -NZBSRUS_APIKEY = None - OMGWTFNZBS = False OMGWTFNZBS_UID = None OMGWTFNZBS_APIKEY = None @@ -192,8 +188,8 @@ ALBUM_COMPLETION_PCT = None # This is used in importer.py to determine how co TORRENTBLACKHOLE_DIR = None NUMBEROFSEEDERS = 10 -ISOHUNT = None KAT = None +KAT_PROXY_URL = None MININOVA = None PIRATEBAY = None PIRATEBAY_PROXY_URL = None @@ -350,12 +346,12 @@ def initialize(): LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, MOVE_FILES, \ RENAME_FILES, FOLDER_FORMAT, FILE_FORMAT, FILE_UNDERSCORES, CLEANUP_FILES, KEEP_NFO, INCLUDE_EXTRAS, EXTRAS, AUTOWANT_UPCOMING, AUTOWANT_ALL, KEEP_TORRENT_FILES, PREFER_TORRENTS, OPEN_MAGNET_LINKS, \ ADD_ALBUM_ART, ALBUM_ART_FORMAT, EMBED_ALBUM_ART, EMBED_LYRICS, REPLACE_EXISTING_FOLDERS, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, SEARCH_INTERVAL, \ - TORRENTBLACKHOLE_DIR, NUMBEROFSEEDERS, ISOHUNT, KAT, PIRATEBAY, PIRATEBAY_PROXY_URL, MININOVA, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, \ + TORRENTBLACKHOLE_DIR, NUMBEROFSEEDERS, KAT, KAT_PROXY_URL, PIRATEBAY, PIRATEBAY_PROXY_URL, MININOVA, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, \ RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, DOWNLOAD_TORRENT_DIR, \ LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, UPDATE_DB_INTERVAL, MB_IGNORE_AGE, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, HEADPHONES_INDEXER, NZBMATRIX, TRANSMISSION_HOST, TRANSMISSION_USERNAME, TRANSMISSION_PASSWORD, \ UTORRENT_HOST, UTORRENT_USERNAME, UTORRENT_PASSWORD, UTORRENT_LABEL, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS, \ - NZBSORG, NZBSORG_UID, NZBSORG_HASH, NZBSRUS, NZBSRUS_UID, NZBSRUS_APIKEY, OMGWTFNZBS, OMGWTFNZBS_UID, OMGWTFNZBS_APIKEY, \ + NZBSORG, NZBSORG_UID, NZBSORG_HASH, OMGWTFNZBS, OMGWTFNZBS_UID, OMGWTFNZBS_APIKEY, \ NZB_DOWNLOADER, TORRENT_DOWNLOADER, PREFERRED_WORDS, REQUIRED_WORDS, IGNORED_WORDS, LASTFM_USERNAME, \ INTERFACE, FOLDER_PERMISSIONS, FILE_PERMISSIONS, ENCODERFOLDER, ENCODER_PATH, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, \ MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, ENCODERVBRCBR, ENCODERLOSSLESS, ENCODER_MULTICORE, ENCODER_MULTICORE_COUNT, DELETE_LOSSLESS_FILES, \ @@ -381,7 +377,6 @@ def initialize(): CheckSection('Headphones') CheckSection('Newznab') CheckSection('NZBsorg') - CheckSection('NZBsRus') CheckSection('omgwtfnzbs') CheckSection('Waffles') CheckSection('Rutracker') @@ -483,8 +478,8 @@ def initialize(): TORRENTBLACKHOLE_DIR = check_setting_str(CFG, 'General', 'torrentblackhole_dir', '') NUMBEROFSEEDERS = check_setting_str(CFG, 'General', 'numberofseeders', '10') - ISOHUNT = bool(check_setting_int(CFG, 'General', 'isohunt', 0)) KAT = bool(check_setting_int(CFG, 'General', 'kat', 0)) + KAT_PROXY_URL = check_setting_str(CFG, 'General', 'kat_proxy_url', '') PIRATEBAY = bool(check_setting_int(CFG, 'General', 'piratebay', 0)) PIRATEBAY_PROXY_URL = check_setting_str(CFG, 'General', 'piratebay_proxy_url', '') MININOVA = bool(check_setting_int(CFG, 'General', 'mininova', 0)) @@ -537,10 +532,6 @@ def initialize(): NZBSORG_UID = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_uid', '') NZBSORG_HASH = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_hash', '') - NZBSRUS = bool(check_setting_int(CFG, 'NZBsRus', 'nzbsrus', 0)) - NZBSRUS_UID = check_setting_str(CFG, 'NZBsRus', 'nzbsrus_uid', '') - NZBSRUS_APIKEY = check_setting_str(CFG, 'NZBsRus', 'nzbsrus_apikey', '') - OMGWTFNZBS = bool(check_setting_int(CFG, 'omgwtfnzbs', 'omgwtfnzbs', 0)) OMGWTFNZBS_UID = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_uid', '') OMGWTFNZBS_APIKEY = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_apikey', '') @@ -703,10 +694,10 @@ def initialize(): CONFIG_VERSION = '3' if CONFIG_VERSION == '3': - #Update the BLACKHOLE option to the NZB_DOWNLOADER format - if BLACKHOLE: - NZB_DOWNLOADER = 2 - CONFIG_VERSION = '4' + #Update the BLACKHOLE option to the NZB_DOWNLOADER format + if BLACKHOLE: + NZB_DOWNLOADER = 2 + CONFIG_VERSION = '4' # Enable Headphones Indexer if they have a VIP account if CONFIG_VERSION == '4': @@ -726,7 +717,6 @@ def initialize(): # Start the logger, disable console if needed logger.initLogger(console=not QUIET, verbose=VERBOSE) - logger.initLogger(console=not QUIET, verbose=False) if not CACHE_DIR: # Put the cache dir in the data dir for now @@ -905,8 +895,8 @@ def config_write(): new_config['General']['numberofseeders'] = NUMBEROFSEEDERS new_config['General']['torrentblackhole_dir'] = TORRENTBLACKHOLE_DIR - new_config['General']['isohunt'] = int(ISOHUNT) new_config['General']['kat'] = int(KAT) + new_config['General']['kat_proxy_url'] = KAT_PROXY_URL new_config['General']['mininova'] = int(MININOVA) new_config['General']['piratebay'] = int(PIRATEBAY) new_config['General']['piratebay_proxy_url'] = PIRATEBAY_PROXY_URL @@ -979,11 +969,6 @@ def config_write(): new_config['NZBsorg']['nzbsorg_uid'] = NZBSORG_UID new_config['NZBsorg']['nzbsorg_hash'] = NZBSORG_HASH - new_config['NZBsRus'] = {} - new_config['NZBsRus']['nzbsrus'] = int(NZBSRUS) - new_config['NZBsRus']['nzbsrus_uid'] = NZBSRUS_UID - new_config['NZBsRus']['nzbsrus_apikey'] = NZBSRUS_APIKEY - new_config['omgwtfnzbs'] = {} new_config['omgwtfnzbs']['omgwtfnzbs'] = int(OMGWTFNZBS) new_config['omgwtfnzbs']['omgwtfnzbs_uid'] = OMGWTFNZBS_UID diff --git a/headphones/albumswitcher.py b/headphones/albumswitcher.py index e9febfd4..34ab7b99 100644 --- a/headphones/albumswitcher.py +++ b/headphones/albumswitcher.py @@ -39,11 +39,11 @@ def switch(AlbumID, ReleaseID): "ReleaseCountry": newalbumdata['ReleaseCountry'], "ReleaseFormat": newalbumdata['ReleaseFormat'] } - + myDB.upsert("albums", newValueDict, controlValueDict) - + for track in newtrackdata: - + controlValueDict = {"TrackID": track['TrackID'], "AlbumID": AlbumID} @@ -60,23 +60,23 @@ def switch(AlbumID, ReleaseID): "Format": track['Format'], "BitRate": track['BitRate'] } - + myDB.upsert("tracks", newValueDict, controlValueDict) - + # Mark albums as downloaded if they have at least 80% (by default, configurable) of the album total_track_count = len(newtrackdata) have_track_count = len(myDB.select('SELECT * from tracks WHERE AlbumID=? AND Location IS NOT NULL', [AlbumID])) - + if oldalbumdata['Status'] == 'Skipped' and ((have_track_count/float(total_track_count)) >= (headphones.ALBUM_COMPLETION_PCT/100.0)): myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', AlbumID]) - + # Update have track counts on index totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [newalbumdata['ArtistID']])) - havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [newalbumdata['ArtistID']])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [newalbumdata['ArtistID']])) + havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [newalbumdata['ArtistID']])) controlValueDict = {"ArtistID": newalbumdata['ArtistID']} - + newValueDict = { "TotalTracks": totaltracks, "HaveTracks": havetracks} - + myDB.upsert("artists", newValueDict, controlValueDict) diff --git a/headphones/api.py b/headphones/api.py index 03888a26..87f20ae0 100644 --- a/headphones/api.py +++ b/headphones/api.py @@ -21,29 +21,29 @@ import lib.simplejson as simplejson from xml.dom.minidom import Document import copy -cmd_list = [ 'getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs', +cmd_list = [ 'getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs', 'findArtist', 'findAlbum', 'addArtist', 'delArtist', 'pauseArtist', 'resumeArtist', 'refreshArtist', - 'addAlbum', 'queueAlbum', 'unqueueAlbum', 'forceSearch', 'forceProcess', 'getVersion', 'checkGithub', - 'shutdown', 'restart', 'update', 'getArtistArt', 'getAlbumArt', 'getArtistInfo', 'getAlbumInfo', + 'addAlbum', 'queueAlbum', 'unqueueAlbum', 'forceSearch', 'forceProcess', 'getVersion', 'checkGithub', + 'shutdown', 'restart', 'update', 'getArtistArt', 'getAlbumArt', 'getArtistInfo', 'getAlbumInfo', 'getArtistThumb', 'getAlbumThumb', 'choose_specific_download', 'download_specific_release'] class Api(object): def __init__(self): - + self.apikey = None self.cmd = None self.id = None - + self.kwargs = None self.data = None self.callback = None - + def checkParams(self,*args,**kwargs): - + if not headphones.API_ENABLED: self.data = 'API not enabled' return @@ -53,32 +53,32 @@ class Api(object): if len(headphones.API_KEY) != 32: self.data = 'API key not generated correctly' return - + if 'apikey' not in kwargs: self.data = 'Missing api key' return - + if kwargs['apikey'] != headphones.API_KEY: self.data = 'Incorrect API key' return else: self.apikey = kwargs.pop('apikey') - + if 'cmd' not in kwargs: self.data = 'Missing parameter: cmd' return - + if kwargs['cmd'] not in cmd_list: self.data = 'Unknown command: %s' % kwargs['cmd'] return else: self.cmd = kwargs.pop('cmd') - + self.kwargs = kwargs self.data = 'OK' def fetchData(self): - + if self.data == 'OK': logger.info('Recieved API command: %s', self.cmd) methodToCall = getattr(self, "_" + self.cmd) @@ -95,74 +95,74 @@ class Api(object): return self.data else: return self.data - + def _dic_from_query(self,query): - + myDB = db.DBConnection() rows = myDB.select(query) - + rows_as_dic = [] - + for row in rows: row_as_dic = dict(zip(row.keys(), row)) rows_as_dic.append(row_as_dic) - + return rows_as_dic - + def _getIndex(self, **kwargs): - + self.data = self._dic_from_query('SELECT * from artists order by ArtistSortName COLLATE NOCASE') - return - + return + def _getArtist(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + artist = self._dic_from_query('SELECT * from artists WHERE ArtistID="' + self.id + '"') albums = self._dic_from_query('SELECT * from albums WHERE ArtistID="' + self.id + '" order by ReleaseDate DESC') description = self._dic_from_query('SELECT * from descriptions WHERE ArtistID="' + self.id + '"') - + self.data = { 'artist': artist, 'albums': albums, 'description' : description } return - + def _getAlbum(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + album = self._dic_from_query('SELECT * from albums WHERE AlbumID="' + self.id + '"') tracks = self._dic_from_query('SELECT * from tracks WHERE AlbumID="' + self.id + '"') description = self._dic_from_query('SELECT * from descriptions WHERE ReleaseGroupID="' + self.id + '"') - + self.data = { 'album' : album, 'tracks' : tracks, 'description' : description } return - + def _getHistory(self, **kwargs): self.data = self._dic_from_query('SELECT * from snatched order by DateAdded DESC') return - + def _getUpcoming(self, **kwargs): self.data = self._dic_from_query("SELECT * from albums WHERE ReleaseDate > date('now') order by ReleaseDate DESC") return - + def _getWanted(self, **kwargs): self.data = self._dic_from_query("SELECT * from albums WHERE Status='Wanted'") return - + def _getSimilar(self, **kwargs): self.data = self._dic_from_query('SELECT * from lastfmcloud') return - + def _getLogs(self, **kwargs): pass - + def _findArtist(self, **kwargs): if 'name' not in kwargs: self.data = 'Missing parameter: name' @@ -171,7 +171,7 @@ class Api(object): limit = kwargs['limit'] else: limit=50 - + self.data = mb.findArtist(kwargs['name'], limit) def _findAlbum(self, **kwargs): @@ -182,216 +182,216 @@ class Api(object): limit = kwargs['limit'] else: limit=50 - + self.data = mb.findRelease(kwargs['name'], limit) - + def _addArtist(self, **kwargs): if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + try: importer.addArtisttoDB(self.id) except Exception, e: self.data = e - + return - + def _delArtist(self, **kwargs): if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + myDB = db.DBConnection() myDB.action('DELETE from artists WHERE ArtistID="' + self.id + '"') myDB.action('DELETE from albums WHERE ArtistID="' + self.id + '"') myDB.action('DELETE from tracks WHERE ArtistID="' + self.id + '"') - + def _pauseArtist(self, **kwargs): if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + myDB = db.DBConnection() controlValueDict = {'ArtistID': self.id} newValueDict = {'Status': 'Paused'} myDB.upsert("artists", newValueDict, controlValueDict) - + def _resumeArtist(self, **kwargs): if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + myDB = db.DBConnection() controlValueDict = {'ArtistID': self.id} newValueDict = {'Status': 'Active'} myDB.upsert("artists", newValueDict, controlValueDict) - + def _refreshArtist(self, **kwargs): if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + try: importer.addArtisttoDB(self.id) except Exception, e: self.data = e - + return - + def _addAlbum(self, **kwargs): if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + try: importer.addReleaseById(self.id) except Exception, e: self.data = e - + return - + def _queueAlbum(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + if 'new' in kwargs: new = kwargs['new'] else: new = False - + if 'lossless' in kwargs: lossless = kwargs['lossless'] else: lossless = False - + myDB = db.DBConnection() controlValueDict = {'AlbumID': self.id} if lossless: newValueDict = {'Status': 'Wanted Lossless'} - else: + else: newValueDict = {'Status': 'Wanted'} myDB.upsert("albums", newValueDict, controlValueDict) - searcher.searchforalbum(self.id, new) - + searcher.searchforalbum(self.id, new) + def _unqueueAlbum(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + myDB = db.DBConnection() controlValueDict = {'AlbumID': self.id} newValueDict = {'Status': 'Skipped'} myDB.upsert("albums", newValueDict, controlValueDict) - + def _forceSearch(self, **kwargs): searcher.searchforalbum() - + def _forceProcess(self, **kwargs): self.dir = None if 'dir' in kwargs: self.dir = kwargs['dir'] postprocessor.forcePostProcess(self.dir) - + def _getVersion(self, **kwargs): - self.data = { + self.data = { 'git_path' : headphones.GIT_PATH, 'install_type' : headphones.INSTALL_TYPE, 'current_version' : headphones.CURRENT_VERSION, 'latest_version' : headphones.LATEST_VERSION, 'commits_behind' : headphones.COMMITS_BEHIND, } - + def _checkGithub(self, **kwargs): versioncheck.checkGithub() self._getVersion() - + def _shutdown(self, **kwargs): headphones.SIGNAL = 'shutdown' - + def _restart(self, **kwargs): headphones.SIGNAL = 'restart' - + def _update(self, **kwargs): headphones.SIGNAL = 'update' - + def _getArtistArt(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + self.data = cache.getArtwork(ArtistID=self.id) def _getAlbumArt(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + self.data = cache.getArtwork(AlbumID=self.id) - + def _getArtistInfo(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + self.data = cache.getInfo(ArtistID=self.id) - + def _getAlbumInfo(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + self.data = cache.getInfo(AlbumID=self.id) - + def _getArtistThumb(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + self.data = cache.getThumb(ArtistID=self.id) def _getAlbumThumb(self, **kwargs): - + if 'id' not in kwargs: self.data = 'Missing parameter: id' return else: self.id = kwargs['id'] - + self.data = cache.getThumb(AlbumID=self.id) def _choose_specific_download(self, **kwargs): @@ -403,9 +403,9 @@ class Api(object): self.id = kwargs['id'] results = searcher.searchforalbum(self.id, choose_specific_download=True) - + results_as_dicts = [] - + for result in results: result_dict = { diff --git a/headphones/cache.py b/headphones/cache.py index a762fd20..4eba6034 100644 --- a/headphones/cache.py +++ b/headphones/cache.py @@ -233,15 +233,15 @@ class Cache(object): return try: - image_url = data['artist']['image'][-1]['#text'] + image_url = data['album']['image'][-1]['#text'] except Exception: - logger.debug('No artist image found') + logger.debug('No album image found on last.fm') image_url = None thumb_url = self._get_thumb_url(data) if not thumb_url: - logger.debug('No artist thumbnail image found') + logger.debug('No album thumbnail image found on last.fm') return {'artwork' : image_url, 'thumbnail' : thumb_url } diff --git a/headphones/classes.py b/headphones/classes.py index cf6534bb..c6b14055 100644 --- a/headphones/classes.py +++ b/headphones/classes.py @@ -1,130 +1,130 @@ -# This file is part of Headphones. -# -# Headphones is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Headphones is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Headphones. If not, see . - -######################################### -## Stolen from Sick-Beard's classes.py ## -######################################### - -import headphones - -import urllib -import datetime - -from common import USER_AGENT - -class HeadphonesURLopener(urllib.FancyURLopener): - version = USER_AGENT - -class AuthURLOpener(HeadphonesURLopener): - """ - URLOpener class that supports http auth without needing interactive password entry. - If the provided username/password don't work it simply fails. - - user: username to use for HTTP auth - pw: password to use for HTTP auth - """ - def __init__(self, user, pw): - self.username = user - self.password = pw - - # remember if we've tried the username/password before - self.numTries = 0 - - # call the base class - urllib.FancyURLopener.__init__(self) - - def prompt_user_passwd(self, host, realm): - """ - Override this function and instead of prompting just give the - username/password that were provided when the class was instantiated. - """ - - # if this is the first try then provide a username/password - if self.numTries == 0: - self.numTries = 1 - return (self.username, self.password) - - # if we've tried before then return blank which cancels the request - else: - return ('', '') - - # this is pretty much just a hack for convenience - def openit(self, url): - self.numTries = 0 - return HeadphonesURLopener.open(self, url) - -class SearchResult: - """ - Represents a search result from an indexer. - """ - - def __init__(self): - self.provider = -1 - - # URL to the NZB/torrent file - self.url = "" - - # used by some providers to store extra info associated with the result - self.extraInfo = [] - - # quality of the release - self.quality = -1 - - # release name - self.name = "" - - def __str__(self): - - if self.provider == None: - return "Invalid provider, unable to print self" - - myString = self.provider.name + " @ " + self.url + "\n" - myString += "Extra Info:\n" - for extra in self.extraInfo: - myString += " " + extra + "\n" - return myString - -class NZBSearchResult(SearchResult): - """ - Regular NZB result with an URL to the NZB - """ - resultType = "nzb" - -class NZBDataSearchResult(SearchResult): - """ - NZB result where the actual NZB XML data is stored in the extraInfo - """ - resultType = "nzbdata" - -class TorrentSearchResult(SearchResult): - """ - Torrent result with an URL to the torrent - """ - resultType = "torrent" - -class Proper: - def __init__(self, name, url, date): - self.name = name - self.url = url - self.date = date - self.provider = None - self.quality = -1 - - self.tvdbid = -1 - self.season = -1 - self.episode = -1 - - def __str__(self): - return str(self.date)+" "+self.name+" "+str(self.season)+"x"+str(self.episode)+" of "+str(self.tvdbid) +# This file is part of Headphones. +# +# Headphones is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Headphones is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Headphones. If not, see . + +######################################### +## Stolen from Sick-Beard's classes.py ## +######################################### + +import headphones + +import urllib +import datetime + +from common import USER_AGENT + +class HeadphonesURLopener(urllib.FancyURLopener): + version = USER_AGENT + +class AuthURLOpener(HeadphonesURLopener): + """ + URLOpener class that supports http auth without needing interactive password entry. + If the provided username/password don't work it simply fails. + + user: username to use for HTTP auth + pw: password to use for HTTP auth + """ + def __init__(self, user, pw): + self.username = user + self.password = pw + + # remember if we've tried the username/password before + self.numTries = 0 + + # call the base class + urllib.FancyURLopener.__init__(self) + + def prompt_user_passwd(self, host, realm): + """ + Override this function and instead of prompting just give the + username/password that were provided when the class was instantiated. + """ + + # if this is the first try then provide a username/password + if self.numTries == 0: + self.numTries = 1 + return (self.username, self.password) + + # if we've tried before then return blank which cancels the request + else: + return ('', '') + + # this is pretty much just a hack for convenience + def openit(self, url): + self.numTries = 0 + return HeadphonesURLopener.open(self, url) + +class SearchResult: + """ + Represents a search result from an indexer. + """ + + def __init__(self): + self.provider = -1 + + # URL to the NZB/torrent file + self.url = "" + + # used by some providers to store extra info associated with the result + self.extraInfo = [] + + # quality of the release + self.quality = -1 + + # release name + self.name = "" + + def __str__(self): + + if self.provider == None: + return "Invalid provider, unable to print self" + + myString = self.provider.name + " @ " + self.url + "\n" + myString += "Extra Info:\n" + for extra in self.extraInfo: + myString += " " + extra + "\n" + return myString + +class NZBSearchResult(SearchResult): + """ + Regular NZB result with an URL to the NZB + """ + resultType = "nzb" + +class NZBDataSearchResult(SearchResult): + """ + NZB result where the actual NZB XML data is stored in the extraInfo + """ + resultType = "nzbdata" + +class TorrentSearchResult(SearchResult): + """ + Torrent result with an URL to the torrent + """ + resultType = "torrent" + +class Proper: + def __init__(self, name, url, date): + self.name = name + self.url = url + self.date = date + self.provider = None + self.quality = -1 + + self.tvdbid = -1 + self.season = -1 + self.episode = -1 + + def __str__(self): + return str(self.date)+" "+self.name+" "+str(self.season)+"x"+str(self.episode)+" of "+str(self.tvdbid) diff --git a/headphones/common.py b/headphones/common.py index c195855f..74aa1db8 100644 --- a/headphones/common.py +++ b/headphones/common.py @@ -1,177 +1,177 @@ -# This file is part of Headphones. -# -# Headphones is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Headphones is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Headphones. If not, see . - -''' -Created on Aug 1, 2011 - -@author: Michael -''' -import platform, operator, os, re - -from headphones import version - -#Identify Our Application -USER_AGENT = 'Headphones/-'+version.HEADPHONES_VERSION+' ('+platform.system()+' '+platform.release()+')' - -### Notification Types -NOTIFY_SNATCH = 1 -NOTIFY_DOWNLOAD = 2 - -notifyStrings = {} -notifyStrings[NOTIFY_SNATCH] = "Started Download" -notifyStrings[NOTIFY_DOWNLOAD] = "Download Finished" - -### Release statuses -UNKNOWN = -1 # should never happen -UNAIRED = 1 # releases that haven't dropped yet -SNATCHED = 2 # qualified with quality -WANTED = 3 # releases we don't have but want to get -DOWNLOADED = 4 # qualified with quality -SKIPPED = 5 # releases we don't want -ARCHIVED = 6 # releases that you don't have locally (counts toward download completion stats) -IGNORED = 7 # releases that you don't want included in your download stats -SNATCHED_PROPER = 9 # qualified with quality - -class Quality: - - NONE = 0 - B192 = 1<<1 # 2 - VBR = 1<<2 # 4 - B256 = 1<<3 # 8 - B320 = 1<<4 #16 - FLAC = 1<<5 #32 - - # put these bits at the other end of the spectrum, far enough out that they shouldn't interfere - UNKNOWN = 1<<15 - - qualityStrings = {NONE: "N/A", - UNKNOWN: "Unknown", - B192: "MP3 192", - VBR: "MP3 VBR", - B256: "MP3 256", - B320: "MP3 320", - FLAC: "Flac"} - - statusPrefixes = {DOWNLOADED: "Downloaded", - SNATCHED: "Snatched"} - - @staticmethod - def _getStatusStrings(status): - toReturn = {} - for x in Quality.qualityStrings.keys(): - toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status]+" ("+Quality.qualityStrings[x]+")" - return toReturn - - @staticmethod - def combineQualities(anyQualities, bestQualities): - anyQuality = 0 - bestQuality = 0 - if anyQualities: - anyQuality = reduce(operator.or_, anyQualities) - if bestQualities: - bestQuality = reduce(operator.or_, bestQualities) - return anyQuality | (bestQuality<<16) - - @staticmethod - def splitQuality(quality): - anyQualities = [] - bestQualities = [] - for curQual in Quality.qualityStrings.keys(): - if curQual & quality: - anyQualities.append(curQual) - if curQual<<16 & quality: - bestQualities.append(curQual) - - return (anyQualities, bestQualities) - - @staticmethod - def nameQuality(name): - - name = os.path.basename(name) - - # if we have our exact text then assume we put it there - for x in Quality.qualityStrings: - if x == Quality.UNKNOWN: - continue - - regex = '\W'+Quality.qualityStrings[x].replace(' ','\W')+'\W' - regex_match = re.search(regex, name, re.I) - if regex_match: - return x - - checkName = lambda list, func: func([re.search(x, name, re.I) for x in list]) - - #TODO: fix quality checking here - if checkName(["mp3", "192"], any) and not checkName(["flac"], all): - return Quality.B192 - elif checkName(["mp3", "256"], any) and not checkName(["flac"], all): - return Quality.B256 - elif checkName(["mp3", "vbr"], any) and not checkName(["flac"], all): - return Quality.VBR - elif checkName(["mp3", "320"], any) and not checkName(["flac"], all): - return Quality.B320 - else: - return Quality.UNKNOWN - - @staticmethod - def assumeQuality(name): - - if name.lower().endswith(".mp3"): - return Quality.MP3 - elif name.lower().endswith(".flac"): - return Quality.LOSSLESS - else: - return Quality.UNKNOWN - - @staticmethod - def compositeStatus(status, quality): - return status + 100 * quality - - @staticmethod - def qualityDownloaded(status): - return (status - DOWNLOADED) / 100 - - @staticmethod - def splitCompositeStatus(status): - """Returns a tuple containing (status, quality)""" - for x in sorted(Quality.qualityStrings.keys(), reverse=True): - if status > x*100: - return (status-x*100, x) - - return (Quality.NONE, status) - - @staticmethod - def statusFromName(name, assume=True): - quality = Quality.nameQuality(name) - if assume and quality == Quality.UNKNOWN: - quality = Quality.assumeQuality(name) - return Quality.compositeStatus(DOWNLOADED, quality) - - DOWNLOADED = None - SNATCHED = None - SNATCHED_PROPER = None - -Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qualityStrings.keys()] -Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()] -Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()] - -MP3 = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR], []) -LOSSLESS = Quality.combineQualities([Quality.FLAC], []) -ANY = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR, Quality.FLAC], []) - -qualityPresets = (MP3, LOSSLESS, ANY) -qualityPresetStrings = {MP3: "MP3 (All bitrates 192+)", - LOSSLESS: "Lossless (flac)", - ANY: "Any"} +# This file is part of Headphones. +# +# Headphones is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Headphones is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Headphones. If not, see . + +''' +Created on Aug 1, 2011 + +@author: Michael +''' +import platform, operator, os, re + +from headphones import version + +#Identify Our Application +USER_AGENT = 'Headphones/-'+version.HEADPHONES_VERSION+' ('+platform.system()+' '+platform.release()+')' + +### Notification Types +NOTIFY_SNATCH = 1 +NOTIFY_DOWNLOAD = 2 + +notifyStrings = {} +notifyStrings[NOTIFY_SNATCH] = "Started Download" +notifyStrings[NOTIFY_DOWNLOAD] = "Download Finished" + +### Release statuses +UNKNOWN = -1 # should never happen +UNAIRED = 1 # releases that haven't dropped yet +SNATCHED = 2 # qualified with quality +WANTED = 3 # releases we don't have but want to get +DOWNLOADED = 4 # qualified with quality +SKIPPED = 5 # releases we don't want +ARCHIVED = 6 # releases that you don't have locally (counts toward download completion stats) +IGNORED = 7 # releases that you don't want included in your download stats +SNATCHED_PROPER = 9 # qualified with quality + +class Quality: + + NONE = 0 + B192 = 1<<1 # 2 + VBR = 1<<2 # 4 + B256 = 1<<3 # 8 + B320 = 1<<4 #16 + FLAC = 1<<5 #32 + + # put these bits at the other end of the spectrum, far enough out that they shouldn't interfere + UNKNOWN = 1<<15 + + qualityStrings = {NONE: "N/A", + UNKNOWN: "Unknown", + B192: "MP3 192", + VBR: "MP3 VBR", + B256: "MP3 256", + B320: "MP3 320", + FLAC: "Flac"} + + statusPrefixes = {DOWNLOADED: "Downloaded", + SNATCHED: "Snatched"} + + @staticmethod + def _getStatusStrings(status): + toReturn = {} + for x in Quality.qualityStrings.keys(): + toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status]+" ("+Quality.qualityStrings[x]+")" + return toReturn + + @staticmethod + def combineQualities(anyQualities, bestQualities): + anyQuality = 0 + bestQuality = 0 + if anyQualities: + anyQuality = reduce(operator.or_, anyQualities) + if bestQualities: + bestQuality = reduce(operator.or_, bestQualities) + return anyQuality | (bestQuality<<16) + + @staticmethod + def splitQuality(quality): + anyQualities = [] + bestQualities = [] + for curQual in Quality.qualityStrings.keys(): + if curQual & quality: + anyQualities.append(curQual) + if curQual<<16 & quality: + bestQualities.append(curQual) + + return (anyQualities, bestQualities) + + @staticmethod + def nameQuality(name): + + name = os.path.basename(name) + + # if we have our exact text then assume we put it there + for x in Quality.qualityStrings: + if x == Quality.UNKNOWN: + continue + + regex = '\W'+Quality.qualityStrings[x].replace(' ','\W')+'\W' + regex_match = re.search(regex, name, re.I) + if regex_match: + return x + + checkName = lambda list, func: func([re.search(x, name, re.I) for x in list]) + + #TODO: fix quality checking here + if checkName(["mp3", "192"], any) and not checkName(["flac"], all): + return Quality.B192 + elif checkName(["mp3", "256"], any) and not checkName(["flac"], all): + return Quality.B256 + elif checkName(["mp3", "vbr"], any) and not checkName(["flac"], all): + return Quality.VBR + elif checkName(["mp3", "320"], any) and not checkName(["flac"], all): + return Quality.B320 + else: + return Quality.UNKNOWN + + @staticmethod + def assumeQuality(name): + + if name.lower().endswith(".mp3"): + return Quality.MP3 + elif name.lower().endswith(".flac"): + return Quality.LOSSLESS + else: + return Quality.UNKNOWN + + @staticmethod + def compositeStatus(status, quality): + return status + 100 * quality + + @staticmethod + def qualityDownloaded(status): + return (status - DOWNLOADED) / 100 + + @staticmethod + def splitCompositeStatus(status): + """Returns a tuple containing (status, quality)""" + for x in sorted(Quality.qualityStrings.keys(), reverse=True): + if status > x*100: + return (status-x*100, x) + + return (Quality.NONE, status) + + @staticmethod + def statusFromName(name, assume=True): + quality = Quality.nameQuality(name) + if assume and quality == Quality.UNKNOWN: + quality = Quality.assumeQuality(name) + return Quality.compositeStatus(DOWNLOADED, quality) + + DOWNLOADED = None + SNATCHED = None + SNATCHED_PROPER = None + +Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qualityStrings.keys()] +Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()] +Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()] + +MP3 = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR], []) +LOSSLESS = Quality.combineQualities([Quality.FLAC], []) +ANY = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR, Quality.FLAC], []) + +qualityPresets = (MP3, LOSSLESS, ANY) +qualityPresetStrings = {MP3: "MP3 (All bitrates 192+)", + LOSSLESS: "Lossless (flac)", + ANY: "Any"} diff --git a/headphones/db.py b/headphones/db.py index 7068ba0e..576d2533 100644 --- a/headphones/db.py +++ b/headphones/db.py @@ -31,7 +31,7 @@ from headphones import logger def dbFilename(filename="headphones.db"): return os.path.join(headphones.DATA_DIR, filename) - + def getCacheSize(): #this will protect against typecasting problems produced by empty string and None settings if not headphones.CACHE_SIZEMB: @@ -42,25 +42,25 @@ def getCacheSize(): class DBConnection: def __init__(self, filename="headphones.db"): - + self.filename = filename self.connection = sqlite3.connect(dbFilename(filename), timeout=20) #don't wait for the disk to finish writing self.connection.execute("PRAGMA synchronous = OFF") #journal disabled since we never do rollbacks - self.connection.execute("PRAGMA journal_mode = %s" % headphones.JOURNAL_MODE) + self.connection.execute("PRAGMA journal_mode = %s" % headphones.JOURNAL_MODE) #64mb of cache memory,probably need to make it user configurable self.connection.execute("PRAGMA cache_size=-%s" % (getCacheSize()*1024)) self.connection.row_factory = sqlite3.Row - + def action(self, query, args=None): if query == None: return - + sqlResult = None attempt = 0 - + while attempt < 5: try: if args == None: @@ -82,28 +82,28 @@ class DBConnection: except sqlite3.DatabaseError, e: logger.error('Fatal Error executing %s :: %s', query, e) raise - + return sqlResult - + def select(self, query, args=None): - + sqlResults = self.action(query, args).fetchall() - + if sqlResults == None: return [] - + return sqlResults - + def upsert(self, tableName, valueDict, keyDict): - + changesBefore = self.connection.total_changes - + genParams = lambda myDict : [x + " = ?" for x in myDict.keys()] - + query = "UPDATE "+tableName+" SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(genParams(keyDict)) - + self.action(query, valueDict.values() + keyDict.values()) - + if self.connection.total_changes == changesBefore: query = "INSERT INTO "+tableName+" (" + ", ".join(valueDict.keys() + keyDict.keys()) + ")" + \ " VALUES (" + ", ".join(["?"] * len(valueDict.keys() + keyDict.keys())) + ")" diff --git a/headphones/exceptions.py b/headphones/exceptions.py index 47de6d99..ecbaa035 100644 --- a/headphones/exceptions.py +++ b/headphones/exceptions.py @@ -1,41 +1,41 @@ -# This file is part of Headphones. -# -# Headphones is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Headphones is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Headphones. If not, see . - -def ex(e): - """ - Returns a string from the exception text if it exists. - """ - - # sanity check - if not e.args or not e.args[0]: - return "" - - e_message = e.args[0] - - # if fixStupidEncodings doesn't fix it then maybe it's not a string, in which case we'll try printing it anyway - if not e_message: - try: - e_message = str(e.args[0]) - except: - e_message = "" - - return e_message - - -class HeadphonesException(Exception): - "Generic Headphones Exception - should never be thrown, only subclassed" - -class NewzbinAPIThrottled(HeadphonesException): - "Newzbin has throttled us, deal with it" +# This file is part of Headphones. +# +# Headphones is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Headphones is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Headphones. If not, see . + +def ex(e): + """ + Returns a string from the exception text if it exists. + """ + + # sanity check + if not e.args or not e.args[0]: + return "" + + e_message = e.args[0] + + # if fixStupidEncodings doesn't fix it then maybe it's not a string, in which case we'll try printing it anyway + if not e_message: + try: + e_message = str(e.args[0]) + except: + e_message = "" + + return e_message + + +class HeadphonesException(Exception): + "Generic Headphones Exception - should never be thrown, only subclassed" + +class NewzbinAPIThrottled(HeadphonesException): + "Newzbin has throttled us, deal with it" diff --git a/headphones/getXldProfile.py b/headphones/getXldProfile.py index 6c744a87..e083d1b6 100755 --- a/headphones/getXldProfile.py +++ b/headphones/getXldProfile.py @@ -8,7 +8,7 @@ from headphones import logger def getXldProfile(xldProfile): xldProfileNotFound = xldProfile expandedPath = os.path.expanduser('~/Library/Preferences/jp.tmkk.XLD.plist') - try: + try: preferences = plistlib.Plist.fromFile(expandedPath) except (expat.ExpatError): os.system("/usr/bin/plutil -convert xml1 %s" % expandedPath ) @@ -61,7 +61,7 @@ def getXldProfile(xldProfile): elif 'TVBR' in ShortDesc: XLDAacOutput2_VBRQuality = int(profile.get('XLDAacOutput2_VBRQuality')) if XLDAacOutput2_VBRQuality > 122: - xldBitrate = 320 + xldBitrate = 320 elif XLDAacOutput2_VBRQuality > 113 and XLDAacOutput2_VBRQuality <= 122: xldBitrate = 285 elif XLDAacOutput2_VBRQuality > 104 and XLDAacOutput2_VBRQuality <= 113: diff --git a/headphones/importer.py b/headphones/importer.py index c0e96310..a94bf35c 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -226,27 +226,27 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False): skip_log = 0 #Make a user configurable variable to skip update of albums with release dates older than this date (in days) pause_delta = headphones.MB_IGNORE_AGE - + rg_exists = myDB.action("SELECT * from albums WHERE AlbumID=?", [rg['id']]).fetchone() if not forcefull: - + new_release_group = False - + try: check_release_date = rg_exists['ReleaseDate'] except TypeError: check_release_date = None new_release_group = True - - + + if new_release_group: - + logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title'])) new_releases = mb.get_new_releases(rgid,includeExtras) - + else: - + if check_release_date is None or check_release_date == u"None": logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title'])) new_releases = mb.get_new_releases(rgid,includeExtras,True) @@ -384,7 +384,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False): # If there's no release in the main albums tables, add the default (hybrid) # If there is a release, check the ReleaseID against the AlbumID to see if they differ (user updated) # check if the album already exists - + if not rg_exists: releaseid = rg['id'] else: @@ -410,7 +410,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False): if rg_exists: newValueDict['DateAdded'] = rg_exists['DateAdded'] newValueDict['Status'] = rg_exists['Status'] - + else: today = helpers.today() @@ -528,10 +528,23 @@ def finalize_update(artistid, artistname, errors=False): myDB.upsert("artists", newValueDict, controlValueDict) -def addReleaseById(rid): +def addReleaseById(rid, rgid=None): myDB = db.DBConnection() + # Create minimum info upfront if added from searchresults + status = '' + if rgid: + dbalbum = myDB.select("SELECT * from albums WHERE AlbumID=?", [rgid]) + if not dbalbum: + status = 'Loading' + controlValueDict = {"AlbumID": rgid} + newValueDict = {"AlbumTitle": rgid, + "ArtistName": status, + "Status": status} + myDB.upsert("albums", newValueDict, controlValueDict) + time.sleep(1) + rgid = None artistid = None release_dict = None @@ -547,9 +560,13 @@ def addReleaseById(rid): release_dict = mb.getRelease(rid) except Exception, e: logger.info('Unable to get release information for Release %s: %s', rid, e) + if status == 'Loading': + myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid]) return if not release_dict: logger.info('Unable to get release information for Release %s: no dict', rid) + if status == 'Loading': + myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid]) return rgid = release_dict['rgid'] @@ -567,7 +584,6 @@ def addReleaseById(rid): else: sortname = release_dict['artist_name'] - logger.info(u"Now manually adding: " + release_dict['artist_name'] + " - with status Paused") controlValueDict = {"ArtistID": release_dict['artist_id']} newValueDict = {"ArtistName": release_dict['artist_name'], @@ -583,12 +599,16 @@ def addReleaseById(rid): elif not artist_exists and not release_dict: logger.error("Artist does not exist in the database and did not get a valid response from MB. Skipping release.") + if status == 'Loading': + myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid]) return - if not rg_exists and release_dict: #it should never be the case that we have an rg and not the artist - #but if it is this will fail + if not rg_exists and release_dict or status == 'Loading' and release_dict: #it should never be the case that we have an rg and not the artist + #but if it is this will fail logger.info(u"Now adding-by-id album (" + release_dict['title'] + ") from id: " + rgid) controlValueDict = {"AlbumID": rgid} + if status != 'Loading': + status = 'Wanted' newValueDict = {"ArtistID": release_dict['artist_id'], "ReleaseID": rgid, @@ -597,8 +617,9 @@ def addReleaseById(rid): "AlbumASIN": release_dict['asin'], "ReleaseDate": release_dict['date'], "DateAdded": helpers.today(), - "Status": 'Wanted', - "Type": release_dict['rg_type'] + "Status": status, + "Type": release_dict['rg_type'], + "ReleaseID": rid } myDB.upsert("albums", newValueDict, controlValueDict) @@ -638,11 +659,19 @@ def addReleaseById(rid): myDB.upsert("tracks", newValueDict, controlValueDict) + # Reset status + if status == 'Loading': + controlValueDict = {"AlbumID": rgid} + newValueDict = {"Status": "Wanted"} + myDB.upsert("albums", newValueDict, controlValueDict) + #start a search for the album import searcher searcher.searchforalbum(rgid, False) elif not rg_exists and not release_dict: logger.error("ReleaseGroup does not exist in the database and did not get a valid response from MB. Skipping release.") + if status == 'Loading': + myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid]) return else: logger.info('Release ' + str(rid) + " already exists in the database!") diff --git a/headphones/librarysync.py b/headphones/librarysync.py index 4a4a4e16..416023fc 100644 --- a/headphones/librarysync.py +++ b/headphones/librarysync.py @@ -27,18 +27,18 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal if cron and not headphones.LIBRARYSCAN: return - + if not dir: if not headphones.MUSIC_DIR: return else: dir = headphones.MUSIC_DIR - + # If we're appending a dir, it's coming from the post processor which is # already bytestring if not append: dir = dir.encode(headphones.SYS_ENCODING) - + if not os.path.isdir(dir): logger.warn('Cannot find directory: %s. Not scanning' % dir.decode(headphones.SYS_ENCODING, 'replace')) return @@ -47,7 +47,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal new_artists = [] logger.info('Scanning music directory: %s' % dir.decode(headphones.SYS_ENCODING, 'replace')) - + if not append: # Clean up bad filepaths tracks = myDB.select('SELECT Location, TrackID from alltracks WHERE Location IS NOT NULL') @@ -57,11 +57,11 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal if not os.path.isfile(encoded_track_string): myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE Location=?', [None, None, None, track['Location']]) myDB.action('UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE Location=?', [None, None, None, track['Location']]) - + del_have_tracks = myDB.select('SELECT Location, Matched, ArtistName from have') for track in del_have_tracks: - encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING) + encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING, 'replace') if not os.path.isfile(encoded_track_string): if track['ArtistName']: #Make sure deleted files get accounted for when updating artist track counts @@ -71,13 +71,13 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal ###############myDB.action('DELETE from have') bitrates = [] - + song_list = [] new_song_count = 0 file_count = 0 latest_subdirectory = [] - + for r,d,f in os.walk(dir): #need to abuse slicing to get a copy of the list, doing it directly will skip the element after a deleted one #using a list comprehension will not work correctly for nested subdirectories (os.walk keeps its original list) @@ -108,11 +108,11 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal except: logger.error('Cannot read file: ' + unicode_song_path) continue - + # Grab the bitrates for the auto detect bit rate option if f.bitrate: bitrates.append(f.bitrate) - + # Use the album artist over the artist if available if f.albumartist: f_artist = f.albumartist @@ -120,8 +120,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal f_artist = f.artist else: f_artist = None - - # Add the song to our song list - + + # Add the song to our song list - # TODO: skip adding songs without the minimum requisite information (just a matter of putting together the right if statements) if f_artist and f.album and f.title: @@ -144,7 +144,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal 'Format' : f.format, 'CleanName' : CleanName } - + #song_list.append(song_dict) check_exist_song = myDB.action("SELECT * FROM have WHERE Location=?", [unicode_song_path]).fetchone() #Only attempt to match songs that are new, haven't yet been matched, or metadata has changed. @@ -182,17 +182,17 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal song_list = myDB.action("SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", [dir.decode(headphones.SYS_ENCODING, 'replace')+"%"]) total_number_of_songs = myDB.action("SELECT COUNT(*) FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", [dir.decode(headphones.SYS_ENCODING, 'replace')+"%"]).fetchone()[0] logger.info("Found " + str(total_number_of_songs) + " new/modified tracks in: '" + dir.decode(headphones.SYS_ENCODING, 'replace') + "'. Matching tracks to the appropriate releases....") - + # Sort the song_list by most vague (e.g. no trackid or releaseid) to most specific (both trackid & releaseid) # When we insert into the database, the tracks with the most specific information will overwrite the more general matches - + ##############song_list = helpers.multikeysort(song_list, ['ReleaseID', 'TrackID']) song_list = helpers.multikeysort(song_list, ['ArtistName', 'AlbumTitle']) - + # We'll use this to give a % completion, just because the track matching might take a while song_count = 0 latest_artist = [] - + for song in song_list: latest_artist.append(song['ArtistName']) @@ -200,26 +200,26 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal logger.info("Now matching songs by %s" % song['ArtistName']) elif latest_artist[song_count] != latest_artist[song_count-1] and song_count !=0: logger.info("Now matching songs by %s" % song['ArtistName']) - + #print song['ArtistName']+' - '+song['AlbumTitle']+' - '+song['TrackTitle'] song_count += 1 completion_percentage = float(song_count)/total_number_of_songs * 100 - + if completion_percentage%10 == 0: logger.info("Track matching is " + str(completion_percentage) + "% complete") - + #THE "MORE-SPECIFIC" CLAUSES HERE HAVE ALL BEEN REMOVED. WHEN RUNNING A LIBRARY SCAN, THE ONLY CLAUSES THAT #EVER GOT HIT WERE [ARTIST/ALBUM/TRACK] OR CLEANNAME. ARTISTID & RELEASEID ARE NEVER PASSED TO THIS FUNCTION, #ARE NEVER FOUND, AND THE OTHER CLAUSES WERE NEVER HIT. FURTHERMORE, OTHER MATCHING FUNCTIONS IN THIS PROGRAM #(IMPORTER.PY, MB.PY) SIMPLY DO A [ARTIST/ALBUM/TRACK] OR CLEANNAME MATCH, SO IT'S ALL CONSISTENT. if song['ArtistName'] and song['AlbumTitle'] and song['TrackTitle']: - + track = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from tracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone() if track: controlValueDict = { 'ArtistName' : track['ArtistName'], 'AlbumTitle' : track['AlbumTitle'], - 'TrackTitle' : track['TrackTitle'] } + 'TrackTitle' : track['TrackTitle'] } newValueDict = { 'Location' : song['Location'], 'BitRate' : song['BitRate'], 'Format' : song['Format'] } @@ -231,10 +231,10 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal else: track = myDB.action('SELECT CleanName, AlbumID from tracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone() if track: - controlValueDict = { 'CleanName' : track['CleanName']} + controlValueDict = { 'CleanName' : track['CleanName']} newValueDict = { 'Location' : song['Location'], 'BitRate' : song['BitRate'], - 'Format' : song['Format'] } + 'Format' : song['Format'] } myDB.upsert("tracks", newValueDict, controlValueDict) controlValueDict2 = { 'Location' : song['Location']} @@ -244,9 +244,9 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal controlValueDict2 = { 'Location' : song['Location']} newValueDict2 = { 'Matched' : "Failed"} myDB.upsert("have", newValueDict2, controlValueDict2) - - alltrack = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from alltracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone() + + alltrack = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from alltracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone() if alltrack: controlValueDict = { 'ArtistName' : alltrack['ArtistName'], 'AlbumTitle' : alltrack['AlbumTitle'], @@ -262,10 +262,10 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal else: alltrack = myDB.action('SELECT CleanName, AlbumID from alltracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone() if alltrack: - controlValueDict = { 'CleanName' : alltrack['CleanName']} + controlValueDict = { 'CleanName' : alltrack['CleanName']} newValueDict = { 'Location' : song['Location'], 'BitRate' : song['BitRate'], - 'Format' : song['Format'] } + 'Format' : song['Format'] } myDB.upsert("alltracks", newValueDict, controlValueDict) controlValueDict2 = { 'Location' : song['Location']} @@ -279,35 +279,35 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal controlValueDict2 = { 'Location' : song['Location']} newValueDict2 = { 'Matched' : "Failed"} myDB.upsert("have", newValueDict2, controlValueDict2) - + #######myDB.action('INSERT INTO have (ArtistName, AlbumTitle, TrackNumber, TrackTitle, TrackLength, BitRate, Genre, Date, TrackID, Location, CleanName, Format) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [song['ArtistName'], song['AlbumTitle'], song['TrackNumber'], song['TrackTitle'], song['TrackLength'], song['BitRate'], song['Genre'], song['Date'], song['TrackID'], song['Location'], CleanName, song['Format']]) logger.info('Completed matching tracks from directory: %s' % dir.decode(headphones.SYS_ENCODING, 'replace')) - + if not append: logger.info('Updating scanned artist track counts') - + # Clean up the new artist list unique_artists = {}.fromkeys(new_artists).keys() current_artists = myDB.select('SELECT ArtistName, ArtistID from artists') - #There was a bug where artists with special characters (-,') would show up in new artists. + #There was a bug where artists with special characters (-,') would show up in new artists. artist_list = [f for f in unique_artists if helpers.cleanName(f).lower() not in [helpers.cleanName(x[0]).lower() for x in current_artists]] artists_checked = [f for f in unique_artists if helpers.cleanName(f).lower() in [helpers.cleanName(x[0]).lower() for x in current_artists]] # Update track counts - + for artist in artists_checked: # Have tracks are selected from tracks table and not all tracks because of duplicates # We update the track count upon an album switch to compliment this havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistName like ? AND Location IS NOT NULL', [artist])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artist])) - #Note, some people complain about having "artist have tracks" > # of tracks total in artist official releases + #Note, some people complain about having "artist have tracks" > # of tracks total in artist official releases # (can fix by getting rid of second len statement) myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistName=?', [havetracks, artist]) - + logger.info('Found %i new artists' % len(artist_list)) - + if len(artist_list): if headphones.ADD_ARTISTS: logger.info('Importing %i new artists' % len(artist_list)) @@ -317,14 +317,14 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal #myDB.action('DELETE from newartists') for artist in artist_list: myDB.action('INSERT OR IGNORE INTO newartists VALUES (?)', [artist]) - + if headphones.DETECT_BITRATE: headphones.PREFERRED_BITRATE = sum(bitrates)/len(bitrates)/1000 - + else: # If we're appending a new album to the database, update the artists total track counts logger.info('Updating artist track counts') - + havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [ArtistID])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [ArtistName])) myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistID=?', [havetracks, ArtistID]) @@ -357,7 +357,7 @@ def update_album_status(AlbumID=None): if album_completion >= headphones.ALBUM_COMPLETION_PCT and album['Status'] == 'Skipped': new_album_status = "Downloaded" - + # I don't think we want to change Downloaded->Skipped..... # I think we can only automatically change Skipped->Downloaded when updating # There was a bug report where this was causing infinite downloads if the album was @@ -369,7 +369,7 @@ def update_album_status(AlbumID=None): # new_album_status = album['Status'] else: new_album_status = album['Status'] - + myDB.upsert("albums", {'Status' : new_album_status}, {'AlbumID' : album['AlbumID']}) if new_album_status != album['Status']: logger.info('Album %s changed to %s' % (album['AlbumTitle'], new_album_status)) diff --git a/headphones/lyrics.py b/headphones/lyrics.py index aa3c952a..ea8458ee 100644 --- a/headphones/lyrics.py +++ b/headphones/lyrics.py @@ -27,26 +27,26 @@ def getLyrics(artist, song): url = 'http://lyrics.wikia.com/api.php' data = request.request_minidom(url, params=params) - + if not data: return - + url = data.getElementsByTagName("url") - + if url: lyricsurl = url[0].firstChild.nodeValue else: logger.info('No lyrics found for %s - %s' % (artist, song)) return - + lyricspage = request.request_content(lyricsurl) - + if not lyricspage: logger.warn('Error fetching lyrics from: %s' % lyricsurl) return m = re.compile('''
.*?
(.*?) # Reconstruct the original comment. self.pieces.append('' % locals()) - + def handle_pi(self, text): # called for each processing instruction, e.g. # Reconstruct original processing instruction. @@ -1942,7 +1942,7 @@ class _BaseHTMLProcessor(sgmllib.SGMLParser): # "http://www.w3.org/TR/html4/loose.dtd"> # Reconstruct original DOCTYPE self.pieces.append('' % locals()) - + _new_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9:]*\s*').match def _scan_name(self, i, declstartpos): rawdata = self.rawdata @@ -2006,7 +2006,7 @@ class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor): data = data.replace('"', '"') data = data.replace(''', "'") return data - + def strattrs(self, attrs): return ''.join([' %s="%s"' % (n,v.replace('"','"')) for n,v in attrs]) @@ -2030,12 +2030,12 @@ class _MicroformatsParser: self.enclosures = [] self.xfn = [] self.vcard = None - + def vcardEscape(self, s): if type(s) in (type(''), type(u'')): s = s.replace(',', '\\,').replace(';', '\\;').replace('\n', '\\n') return s - + def vcardFold(self, s): s = re.sub(';+$', '', s) sFolded = '' @@ -2051,14 +2051,14 @@ class _MicroformatsParser: def normalize(self, s): return re.sub(r'\s+', ' ', s).strip() - + def unique(self, aList): results = [] for element in aList: if element not in results: results.append(element) return results - + def toISO8601(self, dt): return time.strftime('%Y-%m-%dT%H:%M:%SZ', dt) @@ -2148,21 +2148,21 @@ class _MicroformatsParser: def findVCards(self, elmRoot, bAgentParsing=0): sVCards = '' - + if not bAgentParsing: arCards = self.getPropertyValue(elmRoot, 'vcard', bAllowMultiple=1) else: arCards = [elmRoot] - + for elmCard in arCards: arLines = [] - + def processSingleString(sProperty): sValue = self.getPropertyValue(elmCard, sProperty, self.STRING, bAutoEscape=1).decode(self.encoding) if sValue: arLines.append(self.vcardFold(sProperty.upper() + ':' + sValue)) return sValue or u'' - + def processSingleURI(sProperty): sValue = self.getPropertyValue(elmCard, sProperty, self.URI) if sValue: @@ -2185,7 +2185,7 @@ class _MicroformatsParser: if sContentType: sContentType = ';TYPE=' + sContentType.upper() arLines.append(self.vcardFold(sProperty.upper() + sEncoding + sContentType + sValueKey + ':' + sValue)) - + def processTypeValue(sProperty, arDefaultType, arForceType=None): arResults = self.getPropertyValue(elmCard, sProperty, bAllowMultiple=1) for elmResult in arResults: @@ -2197,7 +2197,7 @@ class _MicroformatsParser: sValue = self.getPropertyValue(elmResult, 'value', self.EMAIL, 0) if sValue: arLines.append(self.vcardFold(sProperty.upper() + ';TYPE=' + ','.join(arType) + ':' + sValue)) - + # AGENT # must do this before all other properties because it is destructive # (removes nested class="vcard" nodes so they don't interfere with @@ -2216,10 +2216,10 @@ class _MicroformatsParser: sAgentValue = self.getPropertyValue(elmAgent, 'value', self.URI, bAutoEscape=1); if sAgentValue: arLines.append(self.vcardFold('AGENT;VALUE=uri:' + sAgentValue)) - + # FN (full name) sFN = processSingleString('fn') - + # N (name) elmName = self.getPropertyValue(elmCard, 'n') if elmName: @@ -2228,7 +2228,7 @@ class _MicroformatsParser: arAdditionalNames = self.getPropertyValue(elmName, 'additional-name', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'additional-names', self.STRING, 1, 1) arHonorificPrefixes = self.getPropertyValue(elmName, 'honorific-prefix', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'honorific-prefixes', self.STRING, 1, 1) arHonorificSuffixes = self.getPropertyValue(elmName, 'honorific-suffix', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'honorific-suffixes', self.STRING, 1, 1) - arLines.append(self.vcardFold('N:' + sFamilyName + ';' + + arLines.append(self.vcardFold('N:' + sFamilyName + ';' + sGivenName + ';' + ','.join(arAdditionalNames) + ';' + ','.join(arHonorificPrefixes) + ';' + @@ -2245,25 +2245,25 @@ class _MicroformatsParser: arLines.append(self.vcardFold('N:' + arNames[0] + ';' + arNames[1])) else: arLines.append(self.vcardFold('N:' + arNames[1] + ';' + arNames[0])) - + # SORT-STRING sSortString = self.getPropertyValue(elmCard, 'sort-string', self.STRING, bAutoEscape=1) if sSortString: arLines.append(self.vcardFold('SORT-STRING:' + sSortString)) - + # NICKNAME arNickname = self.getPropertyValue(elmCard, 'nickname', self.STRING, 1, 1) if arNickname: arLines.append(self.vcardFold('NICKNAME:' + ','.join(arNickname))) - + # PHOTO processSingleURI('photo') - + # BDAY dtBday = self.getPropertyValue(elmCard, 'bday', self.DATE) if dtBday: arLines.append(self.vcardFold('BDAY:' + self.toISO8601(dtBday))) - + # ADR (address) arAdr = self.getPropertyValue(elmCard, 'adr', bAllowMultiple=1) for elmAdr in arAdr: @@ -2285,38 +2285,38 @@ class _MicroformatsParser: sRegion + ';' + sPostalCode + ';' + sCountryName)) - + # LABEL processTypeValue('label', ['intl','postal','parcel','work']) - + # TEL (phone number) processTypeValue('tel', ['voice']) - + # EMAIL processTypeValue('email', ['internet'], ['internet']) - + # MAILER processSingleString('mailer') - + # TZ (timezone) processSingleString('tz') - + # GEO (geographical information) elmGeo = self.getPropertyValue(elmCard, 'geo') if elmGeo: sLatitude = self.getPropertyValue(elmGeo, 'latitude', self.STRING, 0, 1) sLongitude = self.getPropertyValue(elmGeo, 'longitude', self.STRING, 0, 1) arLines.append(self.vcardFold('GEO:' + sLatitude + ';' + sLongitude)) - + # TITLE processSingleString('title') - + # ROLE processSingleString('role') # LOGO processSingleURI('logo') - + # ORG (organization) elmOrg = self.getPropertyValue(elmCard, 'org') if elmOrg: @@ -2330,39 +2330,39 @@ class _MicroformatsParser: else: arOrganizationUnit = self.getPropertyValue(elmOrg, 'organization-unit', self.STRING, 1, 1) arLines.append(self.vcardFold('ORG:' + sOrganizationName + ';' + ';'.join(arOrganizationUnit))) - + # CATEGORY arCategory = self.getPropertyValue(elmCard, 'category', self.STRING, 1, 1) + self.getPropertyValue(elmCard, 'categories', self.STRING, 1, 1) if arCategory: arLines.append(self.vcardFold('CATEGORIES:' + ','.join(arCategory))) - + # NOTE processSingleString('note') - + # REV processSingleString('rev') - + # SOUND processSingleURI('sound') - + # UID processSingleString('uid') - + # URL processSingleURI('url') - + # CLASS processSingleString('class') - + # KEY processSingleURI('key') - + if arLines: arLines = [u'BEGIN:vCard',u'VERSION:3.0'] + arLines + [u'END:vCard'] sVCards += u'\n'.join(arLines) + u'\n' - + return sVCards.strip() - + def isProbablyDownloadable(self, elm): attrsD = elm.attrMap if not attrsD.has_key('href'): return 0 @@ -2461,7 +2461,7 @@ class _RelativeURIResolver(_BaseHTMLProcessor): def resolveURI(self, uri): return _makeSafeAbsoluteURI(_urljoin(self.baseuri, uri.strip())) - + def unknown_starttag(self, tag, attrs): if _debug: sys.stderr.write('tag: [%s] with attributes: [%s]\n' % (tag, str(attrs))) @@ -2575,7 +2575,7 @@ class _HTMLSanitizer(_BaseHTMLProcessor): # svgtiny - foreignObject + linearGradient + radialGradient + stop svg_elements = ['a', 'animate', 'animateColor', 'animateMotion', 'animateTransform', 'circle', 'defs', 'desc', 'ellipse', 'foreignObject', - 'font-face', 'font-face-name', 'font-face-src', 'g', 'glyph', 'hkern', + 'font-face', 'font-face-name', 'font-face-src', 'g', 'glyph', 'hkern', 'linearGradient', 'line', 'marker', 'metadata', 'missing-glyph', 'mpath', 'path', 'polygon', 'polyline', 'radialGradient', 'rect', 'set', 'stop', 'svg', 'switch', 'text', 'title', 'tspan', 'use'] @@ -2621,7 +2621,7 @@ class _HTMLSanitizer(_BaseHTMLProcessor): self.unacceptablestack = 0 self.mathmlOK = 0 self.svgOK = 0 - + def unknown_starttag(self, tag, attrs): acceptable_attributes = self.acceptable_attributes keymap = {} @@ -2683,7 +2683,7 @@ class _HTMLSanitizer(_BaseHTMLProcessor): clean_value = self.sanitize_style(value) if clean_value: clean_attrs.append((key,clean_value)) _BaseHTMLProcessor.unknown_starttag(self, tag, clean_attrs) - + def unknown_endtag(self, tag): if not tag in self.acceptable_elements: if tag in self.unacceptable_elements_with_end_tag: @@ -2815,7 +2815,7 @@ class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler http_error_300 = http_error_302 http_error_303 = http_error_302 http_error_307 = http_error_302 - + def http_error_401(self, req, fp, code, msg, headers): # Check if # - server requires digest auth, AND @@ -2914,7 +2914,7 @@ def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, h return opener.open(request, timeout=15) finally: opener.close() # JohnD - + # try to open with native open function (if url_file_stream_or_string is a filename) try: return open(url_file_stream_or_string, 'rb') @@ -2966,7 +2966,7 @@ _date_handlers = [] def registerDateHandler(func): '''Register a date handler function (takes string, returns 9-tuple date in GMT)''' _date_handlers.insert(0, func) - + # ISO-8601 date parsing routines written by Fazal Majid. # The ISO 8601 standard is very convoluted and irregular - a full ISO 8601 # parser is beyond the scope of feedparser and would be a worthwhile addition @@ -2977,7 +2977,7 @@ def registerDateHandler(func): # Please note the order in templates is significant because we need a # greedy match. _iso8601_tmpl = ['YYYY-?MM-?DD', 'YYYY-0MM?-?DD', 'YYYY-MM', 'YYYY-?OOO', - 'YY-?MM-?DD', 'YY-?OOO', 'YYYY', + 'YY-?MM-?DD', 'YY-?OOO', 'YYYY', '-YY-?MM', '-OOO', '-YY', '--MM-?DD', '--MM', '---DD', @@ -3079,7 +3079,7 @@ def _parse_date_iso8601(dateString): # Many implementations have bugs, but we'll pretend they don't. return time.localtime(time.mktime(tuple(tm))) registerDateHandler(_parse_date_iso8601) - + # 8-bit date handling routines written by ytrewq1. _korean_year = u'\ub144' # b3e2 in euc-kr _korean_month = u'\uc6d4' # bff9 in euc-kr @@ -3170,7 +3170,7 @@ _greek_wdays = \ u'\u03a4\u03b5\u03c4': u'Wed', # d4e5f4 in iso-8859-7 u'\u03a0\u03b5\u03bc': u'Thu', # d0e5ec in iso-8859-7 u'\u03a0\u03b1\u03c1': u'Fri', # d0e1f1 in iso-8859-7 - u'\u03a3\u03b1\u03b2': u'Sat', # d3e1e2 in iso-8859-7 + u'\u03a3\u03b1\u03b2': u'Sat', # d3e1e2 in iso-8859-7 } _greek_date_format_re = \ @@ -3360,7 +3360,7 @@ def _parse_date_rfc822(dateString): # 'ET' is equivalent to 'EST', etc. _additional_timezones = {'AT': -400, 'ET': -500, 'CT': -600, 'MT': -700, 'PT': -800} rfc822._timezones.update(_additional_timezones) -registerDateHandler(_parse_date_rfc822) +registerDateHandler(_parse_date_rfc822) def _parse_date_perforce(aDateString): """parse a date in yyyy/mm/dd hh:mm:ss TTT format""" @@ -3398,7 +3398,7 @@ def _getCharacterEncoding(http_headers, xml_data): http_headers is a dictionary xml_data is a raw string (not Unicode) - + This is so much trickier than it sounds, it's not even funny. According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type is application/xml, application/*+xml, @@ -3417,12 +3417,12 @@ def _getCharacterEncoding(http_headers, xml_data): served with a Content-Type of text/* and no charset parameter must be treated as us-ascii. (We now do this.) And also that it must always be flagged as non-well-formed. (We now do this too.) - + If Content-Type is unspecified (input was local file or non-HTTP source) or unrecognized (server just got it totally wrong), then go by the encoding given in the XML prefix of the document and default to 'iso-8859-1' as per the HTTP specification (RFC 2616). - + Then, assuming we didn't find a character encoding in the HTTP headers (and the HTTP Content-type allowed us to look in the body), we need to sniff the first few bytes of the XML data and try to determine @@ -3532,7 +3532,7 @@ def _getCharacterEncoding(http_headers, xml_data): if true_encoding.lower() == 'gb2312': true_encoding = 'gb18030' return true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type - + def _toUTF8(data, encoding): '''Changes an XML data stream on the fly to specify a new encoding @@ -3595,7 +3595,7 @@ def _stripDoctype(data): start = re.search(_s2bytes('<\w'), data) start = start and start.start() or -1 head,data = data[:start+1], data[start+1:] - + entity_pattern = re.compile(_s2bytes(r'^\s*]*?)>'), re.MULTILINE) entity_results=entity_pattern.findall(head) head = entity_pattern.sub(_s2bytes(''), head) @@ -3617,10 +3617,10 @@ def _stripDoctype(data): data = doctype_pattern.sub(replacement, head) + data return version, data, dict(replacement and [(k.decode('utf-8'), v.decode('utf-8')) for k, v in safe_pattern.findall(replacement)]) - + def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, referrer=None, handlers=[], request_headers={}, response_headers={}): '''Parse a feed from a URL, file, stream, or string. - + request_headers, if given, is a dict from http header name to value to add to the request; this overrides internally generated values. ''' @@ -3861,7 +3861,7 @@ class TextSerializer(Serializer): stream.write('\n') except: pass - + class PprintSerializer(Serializer): def write(self, stream=sys.stdout): if self.results.has_key('href'): @@ -3869,7 +3869,7 @@ class PprintSerializer(Serializer): from pprint import pprint pprint(self.results, stream) stream.write('\n') - + if __name__ == '__main__': try: from optparse import OptionParser diff --git a/lib/gntp/core.py b/lib/gntp/core.py index ee544d3d..957b5270 100644 --- a/lib/gntp/core.py +++ b/lib/gntp/core.py @@ -70,7 +70,7 @@ class _GNTPBase(object): 'SHA1': hashlib.sha1, 'SHA256': hashlib.sha256, 'SHA512': hashlib.sha512, - } + } self.headers = {} self.resources = {} diff --git a/lib/httplib2/__init__.py b/lib/httplib2/__init__.py index 01151f7f..58b0de02 100755 --- a/lib/httplib2/__init__.py +++ b/lib/httplib2/__init__.py @@ -3,7 +3,7 @@ from __future__ import generators httplib2 A caching http interface that supports ETags and gzip -to conserve bandwidth. +to conserve bandwidth. Requires Python 2.3 or later @@ -24,8 +24,8 @@ __contributors__ = ["Thomas Broyer (t.broyer@ltgt.net)", __license__ = "MIT" __version__ = "$Rev$" -import re -import sys +import re +import sys import email import email.Utils import email.Message @@ -85,7 +85,7 @@ def has_timeout(timeout): # python 2.6 return (timeout is not None) __all__ = ['Http', 'Response', 'ProxyInfo', 'HttpLib2Error', - 'RedirectMissingLocation', 'RedirectLimit', 'FailedToDecompressContent', + 'RedirectMissingLocation', 'RedirectLimit', 'FailedToDecompressContent', 'UnimplementedDigestAuthOptionError', 'UnimplementedHmacDigestAuthOptionError', 'debuglevel'] @@ -113,8 +113,8 @@ if not hasattr(httplib.HTTPResponse, 'getheaders'): # All exceptions raised here derive from HttpLib2Error class HttpLib2Error(Exception): pass -# Some exceptions can be caught and optionally -# be turned back into responses. +# Some exceptions can be caught and optionally +# be turned back into responses. class HttpLib2ErrorWithResponse(HttpLib2Error): def __init__(self, desc, response, content): self.response = response @@ -176,7 +176,7 @@ def urlnorm(uri): raise RelativeURIError("Only absolute URIs are allowed. uri = %s" % uri) authority = authority.lower() scheme = scheme.lower() - if not path: + if not path: path = "/" # Could do syntax based normalization of the URI before # computing the digest. See Section 6.2.2 of Std 66. @@ -228,7 +228,7 @@ def _parse_cache_control(headers): parts_with_args = [tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=")] parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")] retval = dict(parts_with_args + parts_wo_args) - return retval + return retval # Whether to use a strict mode to parse WWW-Authenticate headers # Might lead to bad results in case of ill-formed header value, @@ -254,10 +254,10 @@ def _parse_www_authenticate(headers, headername='www-authenticate'): while authenticate: # Break off the scheme at the beginning of the line if headername == 'authentication-info': - (auth_scheme, the_rest) = ('digest', authenticate) + (auth_scheme, the_rest) = ('digest', authenticate) else: (auth_scheme, the_rest) = authenticate.split(" ", 1) - # Now loop over all the key value pairs that come after the scheme, + # Now loop over all the key value pairs that come after the scheme, # being careful not to roll into the next scheme match = www_auth.search(the_rest) auth_params = {} @@ -279,17 +279,17 @@ def _entry_disposition(response_headers, request_headers): 1. Cache-Control: max-stale 2. Age: headers are not used in the calculations. - Not that this algorithm is simpler than you might think + Not that this algorithm is simpler than you might think because we are operating as a private (non-shared) cache. This lets us ignore 's-maxage'. We can also ignore 'proxy-invalidate' since we aren't a proxy. - We will never return a stale document as - fresh as a design decision, and thus the non-implementation - of 'max-stale'. This also lets us safely ignore 'must-revalidate' + We will never return a stale document as + fresh as a design decision, and thus the non-implementation + of 'max-stale'. This also lets us safely ignore 'must-revalidate' since we operate as if every server has sent 'must-revalidate'. Since we are private we get to ignore both 'public' and 'private' parameters. We also ignore 'no-transform' since - we don't do any transformations. + we don't do any transformations. The 'no-store' parameter is handled at a higher level. So the only Cache-Control parameters we look at are: @@ -298,7 +298,7 @@ def _entry_disposition(response_headers, request_headers): max-age min-fresh """ - + retval = "STALE" cc = _parse_cache_control(request_headers) cc_response = _parse_cache_control(response_headers) @@ -340,10 +340,10 @@ def _entry_disposition(response_headers, request_headers): min_fresh = int(cc['min-fresh']) except ValueError: min_fresh = 0 - current_age += min_fresh + current_age += min_fresh if freshness_lifetime > current_age: retval = "FRESH" - return retval + return retval def _decompressContent(response, new_content): content = new_content @@ -408,10 +408,10 @@ def _wsse_username_token(cnonce, iso_now, password): return base64.b64encode(_sha("%s%s%s" % (cnonce, iso_now, password)).digest()).strip() -# For credentials we need two things, first +# For credentials we need two things, first # a pool of credential to try (not necesarily tied to BAsic, Digest, etc.) # Then we also need a list of URIs that have already demanded authentication -# That list is tricky since sub-URIs can take the same auth, or the +# That list is tricky since sub-URIs can take the same auth, or the # auth scheme may change as you descend the tree. # So we also need each Auth instance to be able to tell us # how close to the 'top' it is. @@ -443,7 +443,7 @@ class Authentication(object): or such returned from the last authorized response. Over-rise this in sub-classes if necessary. - Return TRUE is the request is to be retried, for + Return TRUE is the request is to be retried, for example Digest may return stale=true. """ return False @@ -461,7 +461,7 @@ class BasicAuthentication(Authentication): class DigestAuthentication(Authentication): - """Only do qop='auth' and MD5, since that + """Only do qop='auth' and MD5, since that is all Apache currently implements""" def __init__(self, credentials, host, request_uri, headers, response, content, http): Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http) @@ -474,7 +474,7 @@ class DigestAuthentication(Authentication): self.challenge['algorithm'] = self.challenge.get('algorithm', 'MD5').upper() if self.challenge['algorithm'] != 'MD5': raise UnimplementedDigestAuthOptionError( _("Unsupported value for algorithm: %s." % self.challenge['algorithm'])) - self.A1 = "".join([self.credentials[0], ":", self.challenge['realm'], ":", self.credentials[1]]) + self.A1 = "".join([self.credentials[0], ":", self.challenge['realm'], ":", self.credentials[1]]) self.challenge['nc'] = 1 def request(self, method, request_uri, headers, content, cnonce = None): @@ -482,17 +482,17 @@ class DigestAuthentication(Authentication): H = lambda x: _md5(x).hexdigest() KD = lambda s, d: H("%s:%s" % (s, d)) A2 = "".join([method, ":", request_uri]) - self.challenge['cnonce'] = cnonce or _cnonce() - request_digest = '"%s"' % KD(H(self.A1), "%s:%s:%s:%s:%s" % (self.challenge['nonce'], - '%08x' % self.challenge['nc'], - self.challenge['cnonce'], + self.challenge['cnonce'] = cnonce or _cnonce() + request_digest = '"%s"' % KD(H(self.A1), "%s:%s:%s:%s:%s" % (self.challenge['nonce'], + '%08x' % self.challenge['nc'], + self.challenge['cnonce'], self.challenge['qop'], H(A2) - )) + )) headers['Authorization'] = 'Digest username="%s", realm="%s", nonce="%s", uri="%s", algorithm=%s, response=%s, qop=%s, nc=%08x, cnonce="%s"' % ( - self.credentials[0], + self.credentials[0], self.challenge['realm'], self.challenge['nonce'], - request_uri, + request_uri, self.challenge['algorithm'], request_digest, self.challenge['qop'], @@ -506,14 +506,14 @@ class DigestAuthentication(Authentication): challenge = _parse_www_authenticate(response, 'www-authenticate').get('digest', {}) if 'true' == challenge.get('stale'): self.challenge['nonce'] = challenge['nonce'] - self.challenge['nc'] = 1 + self.challenge['nc'] = 1 return True else: updated_challenge = _parse_www_authenticate(response, 'authentication-info').get('digest', {}) if updated_challenge.has_key('nextnonce'): self.challenge['nonce'] = updated_challenge['nextnonce'] - self.challenge['nc'] = 1 + self.challenge['nc'] = 1 return False @@ -562,11 +562,11 @@ class HmacDigestAuthentication(Authentication): request_digest = "%s:%s:%s:%s:%s" % (method, request_uri, cnonce, self.challenge['snonce'], headers_val) request_digest = hmac.new(self.key, request_digest, self.hashmod).hexdigest().lower() headers['Authorization'] = 'HMACDigest username="%s", realm="%s", snonce="%s", cnonce="%s", uri="%s", created="%s", response="%s", headers="%s"' % ( - self.credentials[0], + self.credentials[0], self.challenge['realm'], self.challenge['snonce'], cnonce, - request_uri, + request_uri, created, request_digest, keylist, @@ -583,7 +583,7 @@ class WsseAuthentication(Authentication): """This is thinly tested and should not be relied upon. At this time there isn't any third party server to test against. Blogger and TypePad implemented this algorithm at one point - but Blogger has since switched to Basic over HTTPS and + but Blogger has since switched to Basic over HTTPS and TypePad has implemented it wrong, by never issuing a 401 challenge but instead requiring your client to telepathically know that their endpoint is expecting WSSE profile="UsernameToken".""" @@ -629,7 +629,7 @@ class GoogleLoginAuthentication(Authentication): def request(self, method, request_uri, headers, content): """Modify the request headers to add the appropriate Authorization header.""" - headers['authorization'] = 'GoogleLogin Auth=' + self.Auth + headers['authorization'] = 'GoogleLogin Auth=' + self.Auth AUTH_SCHEME_CLASSES = { @@ -644,13 +644,13 @@ AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"] class FileCache(object): """Uses a local directory as a store for cached files. - Not really safe to use if multiple threads or processes are going to + Not really safe to use if multiple threads or processes are going to be running on the same cache. """ def __init__(self, cache, safe=safename): # use safe=lambda x: md5.new(x).hexdigest() for the old behavior self.cache = cache self.safe = safe - if not os.path.exists(cache): + if not os.path.exists(cache): os.makedirs(self.cache) def get(self, key): @@ -688,7 +688,7 @@ class Credentials(object): def iter(self, domain): for (cdomain, name, password) in self.credentials: if cdomain == "" or domain == cdomain: - yield (name, password) + yield (name, password) class KeyCerts(Credentials): """Identical to Credentials except that @@ -772,7 +772,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): sock.setproxy(*self.proxy_info.astuple()) else: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - + if has_timeout(self.timeout): sock.settimeout(self.timeout) sock.connect((self.host, self.port)) @@ -820,7 +820,7 @@ the same interface as FileCache.""" # If set to False then no redirects are followed, even safe ones. self.follow_redirects = True - + # Which HTTP methods do we apply optimistic concurrency to, i.e. # which methods get an "if-match:" etag header added to them. self.optimistic_concurrency_methods = ["PUT"] @@ -831,7 +831,7 @@ the same interface as FileCache.""" self.ignore_etag = False - self.force_exception_to_status_code = False + self.force_exception_to_status_code = False self.timeout = timeout @@ -908,12 +908,12 @@ the same interface as FileCache.""" auths = [(auth.depth(request_uri), auth) for auth in self.authorizations if auth.inscope(host, request_uri)] auth = auths and sorted(auths)[0][1] or None - if auth: + if auth: auth.request(method, request_uri, headers, body) (response, content) = self._conn_request(conn, request_uri, method, body, headers) - if auth: + if auth: if auth.response(response, body): auth.request(method, request_uri, headers, body) (response, content) = self._conn_request(conn, request_uri, method, body, headers ) @@ -921,7 +921,7 @@ the same interface as FileCache.""" if response.status == 401: for authorization in self._auth_from_challenge(host, request_uri, headers, response, content): - authorization.request(method, request_uri, headers, body) + authorization.request(method, request_uri, headers, body) (response, content) = self._conn_request(conn, request_uri, method, body, headers, ) if response.status != 401: self.authorizations.append(authorization) @@ -944,7 +944,7 @@ the same interface as FileCache.""" if response.status == 301 and method in ["GET", "HEAD"]: response['-x-permanent-redirect-url'] = response['location'] if not response.has_key('content-location'): - response['content-location'] = absolute_uri + response['content-location'] = absolute_uri _updateCache(headers, response, content, self.cache, cachekey) if headers.has_key('if-none-match'): del headers['if-none-match'] @@ -954,7 +954,7 @@ the same interface as FileCache.""" location = response['location'] old_response = copy.deepcopy(response) if not old_response.has_key('content-location'): - old_response['content-location'] = absolute_uri + old_response['content-location'] = absolute_uri redirect_method = ((response.status == 303) and (method not in ["GET", "HEAD"])) and "GET" or method (response, content) = self.request(location, redirect_method, body=body, headers = headers, redirections = redirections - 1) response.previous = old_response @@ -963,7 +963,7 @@ the same interface as FileCache.""" elif response.status in [200, 203] and method == "GET": # Don't cache 206's since we aren't going to handle byte range requests if not response.has_key('content-location'): - response['content-location'] = absolute_uri + response['content-location'] = absolute_uri _updateCache(headers, response, content, self.cache, cachekey) return (response, content) @@ -978,10 +978,10 @@ the same interface as FileCache.""" def request(self, uri, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None): """ Performs a single HTTP request. -The 'uri' is the URI of the HTTP resource and can begin +The 'uri' is the URI of the HTTP resource and can begin with either 'http' or 'https'. The value of 'uri' must be an absolute URI. -The 'method' is the HTTP method to perform, such as GET, POST, DELETE, etc. +The 'method' is the HTTP method to perform, such as GET, POST, DELETE, etc. There is no restriction on the methods allowed. The 'body' is the entity body to be sent with the request. It is a string @@ -990,11 +990,11 @@ object. Any extra headers that are to be sent with the request should be provided in the 'headers' dictionary. -The maximum number of redirect to follow before raising an +The maximum number of redirect to follow before raising an exception is 'redirections. The default is 5. -The return value is a tuple of (response, content), the first -being and instance of the 'Response' class, the second being +The return value is a tuple of (response, content), the first +being and instance of the 'Response' class, the second being a string that contains the response entity body. """ try: @@ -1085,13 +1085,13 @@ a string that contains the response entity body. # Determine our course of action: # Is the cached entry fresh or stale? # Has the client requested a non-cached response? - # - # There seems to be three possible answers: + # + # There seems to be three possible answers: # 1. [FRESH] Return the cache entry w/o doing a GET # 2. [STALE] Do the GET (but add in cache validators if available) # 3. [TRANSPARENT] Do a GET w/o any cache validators (Cache-Control: no-cache) on the request - entry_disposition = _entry_disposition(info, headers) - + entry_disposition = _entry_disposition(info, headers) + if entry_disposition == "FRESH": if not cached_value: info['status'] = '504' @@ -1113,7 +1113,7 @@ a string that contains the response entity body. if response.status == 304 and method == "GET": # Rewrite the cache entry with the new end-to-end headers - # Take all headers that are in response + # Take all headers that are in response # and overwrite their values in info. # unless they are hop-by-hop, or are listed in the connection header. @@ -1125,14 +1125,14 @@ a string that contains the response entity body. _updateCache(headers, merged_response, content, self.cache, cachekey) response = merged_response response.status = 200 - response.fromcache = True + response.fromcache = True elif response.status == 200: content = new_content else: self.cache.delete(cachekey) - content = new_content - else: + content = new_content + else: cc = _parse_cache_control(headers) if cc.has_key('only-if-cached'): info['status'] = '504' @@ -1146,7 +1146,7 @@ a string that contains the response entity body. response = e.response content = e.content response.status = 500 - response.reason = str(e) + response.reason = str(e) elif isinstance(e, socket.timeout) or (isinstance(e, socket.error) and 'timed out' in str(e)): content = "Request Timeout" response = Response( { @@ -1156,24 +1156,24 @@ a string that contains the response entity body. }) response.reason = "Request Timeout" else: - content = str(e) + content = str(e) response = Response( { "content-type": "text/plain", "status": "400", "content-length": len(content) }) - response.reason = "Bad Request" + response.reason = "Bad Request" else: raise - + return (response, content) - + class Response(dict): """An object more like email.Message than httplib.HTTPResponse.""" - + """Is this response from our local cache""" fromcache = False @@ -1189,27 +1189,27 @@ class Response(dict): previous = None def __init__(self, info): - # info is either an email.Message or + # info is either an email.Message or # an httplib.HTTPResponse object. if isinstance(info, httplib.HTTPResponse): - for key, value in info.getheaders(): - self[key.lower()] = value + for key, value in info.getheaders(): + self[key.lower()] = value self.status = info.status self['status'] = str(self.status) self.reason = info.reason self.version = info.version elif isinstance(info, email.Message.Message): - for key, value in info.items(): - self[key] = value + for key, value in info.items(): + self[key] = value self.status = int(self['status']) else: - for key, value in info.iteritems(): - self[key] = value + for key, value in info.iteritems(): + self[key] = value self.status = int(self.get('status', self.status)) def __getattr__(self, name): if name == 'dict': - return self - else: - raise AttributeError, name + return self + else: + raise AttributeError, name diff --git a/lib/httplib2/iri2uri.py b/lib/httplib2/iri2uri.py index 70667edf..e4cda1d7 100755 --- a/lib/httplib2/iri2uri.py +++ b/lib/httplib2/iri2uri.py @@ -16,7 +16,7 @@ import urlparse # Convert an IRI to a URI following the rules in RFC 3987 -# +# # The characters we need to enocde and escape are defined in the spec: # # iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD @@ -49,7 +49,7 @@ escape_range = [ (0xF0000, 0xFFFFD ), (0x100000, 0x10FFFD) ] - + def encode(c): retval = c i = ord(c) @@ -63,19 +63,19 @@ def encode(c): def iri2uri(uri): - """Convert an IRI to a URI. Note that IRIs must be + """Convert an IRI to a URI. Note that IRIs must be passed in a unicode strings. That is, do not utf-8 encode - the IRI before passing it into the function.""" + the IRI before passing it into the function.""" if isinstance(uri ,unicode): (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri) authority = authority.encode('idna') # For each character in 'ucschar' or 'iprivate' # 1. encode as utf-8 - # 2. then %-encode each octet of that utf-8 + # 2. then %-encode each octet of that utf-8 uri = urlparse.urlunsplit((scheme, authority, path, query, fragment)) uri = "".join([encode(c) for c in uri]) return uri - + if __name__ == "__main__": import unittest @@ -83,7 +83,7 @@ if __name__ == "__main__": def test_uris(self): """Test that URIs are invariant under the transformation.""" - invariant = [ + invariant = [ u"ftp://ftp.is.co.za/rfc/rfc1808.txt", u"http://www.ietf.org/rfc/rfc2396.txt", u"ldap://[2001:db8::7]/c=GB?objectClass?one", @@ -94,7 +94,7 @@ if __name__ == "__main__": u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ] for uri in invariant: self.assertEqual(uri, iri2uri(uri)) - + def test_iri(self): """ Test that the right type of escaping is done for each part of the URI.""" self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}")) @@ -107,4 +107,4 @@ if __name__ == "__main__": unittest.main() - + diff --git a/lib/mako/ext/autohandler.py b/lib/mako/ext/autohandler.py index d56cbc1e..7bd1c71f 100644 --- a/lib/mako/ext/autohandler.py +++ b/lib/mako/ext/autohandler.py @@ -48,7 +48,7 @@ def autohandler(template, context, name='autohandler'): if len(tokens) == 1: break tokens[-2:] = [name] - + if not lookup.filesystem_checks: return lookup._uri_cache.setdefault( (autohandler, _template_uri, name), None) @@ -62,4 +62,4 @@ def _file_exists(lookup, path): return True else: return False - + diff --git a/lib/mako/ext/preprocessors.py b/lib/mako/ext/preprocessors.py index 5a15ff35..acd32e55 100644 --- a/lib/mako/ext/preprocessors.py +++ b/lib/mako/ext/preprocessors.py @@ -4,16 +4,16 @@ # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -"""preprocessing functions, used with the 'preprocessor' +"""preprocessing functions, used with the 'preprocessor' argument on Template, TemplateLookup""" import re def convert_comments(text): """preprocess old style comments. - + example: - + from mako.ext.preprocessors import convert_comments t = Template(..., preprocessor=preprocess_comments)""" return re.sub(r'(?<=\n)\s*#[^#]', "##", text) diff --git a/lib/musicbrainzngs/musicbrainz.py b/lib/musicbrainzngs/musicbrainz.py index 65454834..16492087 100644 --- a/lib/musicbrainzngs/musicbrainz.py +++ b/lib/musicbrainzngs/musicbrainz.py @@ -279,7 +279,7 @@ def auth(u, p): global user, password user = u password = p - + def hpauth(u, p): """Set the username and password to be used in subsequent queries to the MusicBrainz XML API that require authentication. @@ -574,7 +574,7 @@ def _mb_request(path, method='GET', auth_required=False, client_required=False, whether exceptions should be raised if the client and username/password are left unspecified, respectively. """ - global parser_fun + global parser_fun if args is None: args = {} @@ -638,7 +638,7 @@ def _mb_request(path, method='GET', auth_required=False, client_required=False, if hostname == '144.76.94.239:8181': base64string = base64.encodestring('%s:%s' % (hpuser, hppassword)).replace('\n', '') req.add_header("Authorization", "Basic %s" % base64string) - + _log.debug("requesting with UA %s" % _useragent) if body: req.add_header('Content-Type', 'application/xml; charset=UTF-8') @@ -908,7 +908,7 @@ def get_releases_by_discid(id, includes=[], toc=None, cdstubs=True): The `toc` should have to same format as :attr:`discid.Disc.toc_string`. If no toc matches in musicbrainz but a :musicbrainz:`CD Stub` does, - the CD Stub will be returned. Prevent this from happening by + the CD Stub will be returned. Prevent this from happening by passing `cdstubs=False`. The result is a dict with either a 'disc' , a 'cdstub' key diff --git a/lib/mutagen/easymp4.py b/lib/mutagen/easymp4.py index 3abacccc..65e78b74 100644 --- a/lib/mutagen/easymp4.py +++ b/lib/mutagen/easymp4.py @@ -22,7 +22,7 @@ class EasyMP4Tags(DictMixin, Metadata): strings, and values are a list of Unicode strings (and these lists are always of length 0 or 1). - If you need access to the full MP4 metadata feature set, you should use + If you need access to the full MP4 metadata feature set, you should use MP4, not EasyMP4. """ diff --git a/lib/oauth2/__init__.py b/lib/oauth2/__init__.py index 2b938909..cf77705d 100644 --- a/lib/oauth2/__init__.py +++ b/lib/oauth2/__init__.py @@ -85,11 +85,11 @@ def generate_verifier(length=8): class Consumer(object): """A consumer of OAuth-protected services. - + The OAuth consumer is a "third-party" service that wants to access protected resources from an OAuth service provider on behalf of an end user. It's kind of the OAuth client. - + Usually a consumer must be registered with the service provider by the developer of the consumer software. As part of that process, the service provider gives the consumer a *key* and a *secret* with which the consumer @@ -97,7 +97,7 @@ class Consumer(object): key in each request to identify itself, but will use its secret only when signing requests, to prove that the request is from that particular registered consumer. - + Once registered, the consumer can then use its consumer credentials to ask the service provider for a request token, kicking off the OAuth authorization process. @@ -125,12 +125,12 @@ class Consumer(object): class Token(object): """An OAuth credential used to request authorization or a protected resource. - + Tokens in OAuth comprise a *key* and a *secret*. The key is included in requests to identify the token being used, but the secret is used only in the signature, to prove that the requester is who the server gave the token to. - + When first negotiating the authorization, the consumer asks for a *request token* that the live user authorizes with the service provider. The consumer then exchanges the request token for an *access token* that can @@ -175,7 +175,7 @@ class Token(object): def to_string(self): """Returns this token as a plain string, suitable for storage. - + The resulting string includes the token's secret, so you should never send or store this string where a third party can read it. """ @@ -188,7 +188,7 @@ class Token(object): if self.callback_confirmed is not None: data['oauth_callback_confirmed'] = self.callback_confirmed return urllib.urlencode(data) - + @staticmethod def from_string(s): """Deserializes a token from a string like one returned by @@ -209,7 +209,7 @@ class Token(object): try: secret = params['oauth_token_secret'][0] except Exception: - raise ValueError("'oauth_token_secret' not found in " + raise ValueError("'oauth_token_secret' not found in " "OAuth request.") token = Token(key, secret) @@ -225,45 +225,45 @@ class Token(object): def setter(attr): name = attr.__name__ - + def getter(self): try: return self.__dict__[name] except KeyError: raise AttributeError(name) - + def deleter(self): del self.__dict__[name] - + return property(getter, attr, deleter) class Request(dict): - + """The parameters and information for an HTTP request, suitable for authorizing with OAuth credentials. - + When a consumer wants to access a service's protected resources, it does so using a signed HTTP request identifying itself (the consumer) with its key, and providing an access token authorized by the end user to access those resources. - + """ - + http_method = HTTP_METHOD http_url = None version = VERSION - + def __init__(self, method=HTTP_METHOD, url=None, parameters=None): if method is not None: self.method = method - + if url is not None: self.url = url - + if parameters is not None: self.update(parameters) - + @setter def url(self, value): parts = urlparse.urlparse(value) @@ -280,33 +280,33 @@ class Request(dict): value = '%s://%s%s' % (scheme, netloc, path) self.__dict__['url'] = value - + @setter def method(self, value): self.__dict__['method'] = value.upper() - + def _get_timestamp_nonce(self): return self['oauth_timestamp'], self['oauth_nonce'] - + def get_nonoauth_parameters(self): """Get any non-OAuth parameters.""" - return dict([(k, v) for k, v in self.iteritems() + return dict([(k, v) for k, v in self.iteritems() if not k.startswith('oauth_')]) - + def to_header(self, realm=''): """Serialize as a header for an HTTPAuth request.""" - oauth_params = ((k, v) for k, v in self.items() + oauth_params = ((k, v) for k, v in self.items() if k.startswith('oauth_')) stringy_params = ((k, escape(str(v))) for k, v in oauth_params) header_params = ('%s="%s"' % (k, v) for k, v in stringy_params) params_header = ', '.join(header_params) - + auth_header = 'OAuth realm="%s"' % realm if params_header: auth_header = "%s, %s" % (auth_header, params_header) - + return {'Authorization': auth_header} - + def to_postdata(self): """Serialize as post data for a POST request.""" return self.encode_postdata(self) @@ -327,7 +327,7 @@ class Request(dict): raise Error('Parameter not found: %s' % parameter) return ret - + def get_normalized_parameters(self): """Return a string that contains the parameters that must be signed.""" items = [(k, v) for k, v in self.items() if k != 'oauth_signature'] @@ -337,7 +337,7 @@ class Request(dict): # (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6) # Spaces must be encoded with "%20" instead of "+" return encoded_str.replace('+', '%20') - + def sign_request(self, signature_method, consumer, token): """Set the signature parameter to the result of sign.""" @@ -349,24 +349,24 @@ class Request(dict): self['oauth_signature_method'] = signature_method.name self['oauth_signature'] = signature_method.sign(self, consumer, token) - + @classmethod def make_timestamp(cls): """Get seconds since epoch (UTC).""" return str(int(time.time())) - + @classmethod def make_nonce(cls): """Generate pseudorandom number.""" return str(random.randint(0, 100000000)) - + @classmethod def from_request(cls, http_method, http_url, headers=None, parameters=None, query_string=None): """Combines multiple parameter sources.""" if parameters is None: parameters = {} - + # Headers if headers and 'Authorization' in headers: auth_header = headers['Authorization'] @@ -380,57 +380,57 @@ class Request(dict): except: raise Error('Unable to parse OAuth parameters from ' 'Authorization header.') - + # GET or POST query string. if query_string: query_params = cls._split_url_string(query_string) parameters.update(query_params) - + # URL parameters. param_str = urlparse.urlparse(http_url)[4] # query url_params = cls._split_url_string(param_str) parameters.update(url_params) - + if parameters: return cls(http_method, http_url, parameters) - + return None - + @classmethod def from_consumer_and_token(cls, consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): if not parameters: parameters = {} - + defaults = { 'oauth_consumer_key': consumer.key, 'oauth_timestamp': cls.make_timestamp(), 'oauth_nonce': cls.make_nonce(), 'oauth_version': cls.version, } - + defaults.update(parameters) parameters = defaults - + if token: parameters['oauth_token'] = token.key - + return Request(http_method, http_url, parameters) - + @classmethod - def from_token_and_callback(cls, token, callback=None, + def from_token_and_callback(cls, token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): if not parameters: parameters = {} - + parameters['oauth_token'] = token.key - + if callback: parameters['oauth_callback'] = callback - + return cls(http_method, http_url, parameters) - + @staticmethod def _split_header(header): """Turn Authorization: header into parameters.""" @@ -447,7 +447,7 @@ class Request(dict): # Remove quotes and unescape the value. params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) return params - + @staticmethod def _split_url_string(param_str): """Turn URL string into parameters.""" @@ -460,7 +460,7 @@ class Request(dict): class Server(object): """A skeletal implementation of a service provider, providing protected resources to requests from authorized consumers. - + This class implements the logic to check requests for authorization. You can use it with your web server or web framework to protect certain resources with OAuth. @@ -536,7 +536,7 @@ class Server(object): if not valid: key, base = signature_method.signing_base(request, consumer, token) - raise Error('Invalid signature. Expected signature base ' + raise Error('Invalid signature. Expected signature base ' 'string: %s' % base) built = signature_method.sign(request, consumer, token) @@ -567,7 +567,7 @@ class Client(httplib2.Http): self.token = token self.method = SignatureMethod_HMAC_SHA1() - httplib2.Http.__init__(self, cache=cache, timeout=timeout, + httplib2.Http.__init__(self, cache=cache, timeout=timeout, proxy_info=proxy_info) def set_signature_method(self, method): @@ -576,10 +576,10 @@ class Client(httplib2.Http): self.method = method - def request(self, uri, method="GET", body=None, headers=None, + def request(self, uri, method="GET", body=None, headers=None, redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None, force_auth_header=False): - + if not isinstance(headers, dict): headers = {} @@ -587,7 +587,7 @@ class Client(httplib2.Http): parameters = dict(parse_qsl(body)) elif method == "GET": parsed = urlparse.urlparse(uri) - parameters = parse_qs(parsed.query) + parameters = parse_qs(parsed.query) else: parameters = None @@ -614,14 +614,14 @@ class Client(httplib2.Http): # don't call update twice. headers.update(req.to_header()) - return httplib2.Http.request(self, uri, method=method, body=body, - headers=headers, redirections=redirections, + return httplib2.Http.request(self, uri, method=method, body=body, + headers=headers, redirections=redirections, connection_type=connection_type) class SignatureMethod(object): """A way of signing requests. - + The OAuth protocol lets consumers and service providers pick a way to sign requests. This interface shows the methods expected by the other `oauth` modules for signing requests. Subclass it and implement its methods to @@ -657,7 +657,7 @@ class SignatureMethod(object): class SignatureMethod_HMAC_SHA1(SignatureMethod): name = 'HMAC-SHA1' - + def signing_base(self, request, consumer, token): sig = ( escape(request.method), diff --git a/lib/ordereddict.py b/lib/ordereddict.py index 5b0303f5..7242b506 100644 --- a/lib/ordereddict.py +++ b/lib/ordereddict.py @@ -1,127 +1,127 @@ -# Copyright (c) 2009 Raymond Hettinger -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. - -from UserDict import DictMixin - -class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.__map = {} # key --> [key, prev, next] - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = reversed(self).next() - else: - key = iter(self).next() - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - if len(self) != len(other): - return False - for p, q in zip(self.items(), other.items()): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other +# Copyright (c) 2009 Raymond Hettinger +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +from UserDict import DictMixin + +class OrderedDict(dict, DictMixin): + + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__end + except AttributeError: + self.clear() + self.update(*args, **kwds) + + def clear(self): + self.__end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.__map = {} # key --> [key, prev, next] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + end = self.__end + curr = end[1] + curr[2] = end[1] = self.__map[key] = [key, curr, end] + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + key, prev, next = self.__map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.__end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.__end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def popitem(self, last=True): + if not self: + raise KeyError('dictionary is empty') + if last: + key = reversed(self).next() + else: + key = iter(self).next() + value = self.pop(key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + tmp = self.__map, self.__end + del self.__map, self.__end + inst_dict = vars(self).copy() + self.__map, self.__end = tmp + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def keys(self): + return list(self) + + setdefault = DictMixin.setdefault + update = DictMixin.update + pop = DictMixin.pop + values = DictMixin.values + items = DictMixin.items + iterkeys = DictMixin.iterkeys + itervalues = DictMixin.itervalues + iteritems = DictMixin.iteritems + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def copy(self): + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + if isinstance(other, OrderedDict): + if len(self) != len(other): + return False + for p, q in zip(self.items(), other.items()): + if p != q: + return False + return True + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other diff --git a/lib/pyItunes/Library.py b/lib/pyItunes/Library.py index 2400c969..3118fb7b 100644 --- a/lib/pyItunes/Library.py +++ b/lib/pyItunes/Library.py @@ -36,6 +36,6 @@ class Library: if attributes.get('Play Count'): s.play_count = int(attributes.get('Play Count')) if attributes.get('Location'): - s.location = attributes.get('Location') + s.location = attributes.get('Location') songs.append(s) return songs \ No newline at end of file diff --git a/lib/pyItunes/Song.py b/lib/pyItunes/Song.py index 27d44d79..c59759e6 100644 --- a/lib/pyItunes/Song.py +++ b/lib/pyItunes/Song.py @@ -42,5 +42,5 @@ class Song: album_rating = None play_count = None location = None - + #title = property(getTitle,setTitle) \ No newline at end of file diff --git a/lib/pyItunes/XMLLibraryParser.py b/lib/pyItunes/XMLLibraryParser.py index 7e4b239a..6824aee7 100644 --- a/lib/pyItunes/XMLLibraryParser.py +++ b/lib/pyItunes/XMLLibraryParser.py @@ -5,7 +5,7 @@ class XMLLibraryParser: s = f.read() lines = s.split("\n") self.dictionary = self.parser(lines) - + def getValue(self,restOfLine): value = re.sub("<.*?>","",restOfLine) u = unicode(value,"utf-8") diff --git a/lib/pygazelle/api.py b/lib/pygazelle/api.py index 3bdd7c2e..91df1ad2 100644 --- a/lib/pygazelle/api.py +++ b/lib/pygazelle/api.py @@ -201,7 +201,7 @@ class GazelleAPI(object): Returns the inbox Mailbox for the logged in user """ return Mailbox(self, 'inbox', page, sort) - + def get_sentbox(self, page='1', sort='unread'): """ Returns the sentbox Mailbox for the logged in user diff --git a/lib/pygazelle/inbox.py b/lib/pygazelle/inbox.py index e5016286..a26c9a26 100644 --- a/lib/pygazelle/inbox.py +++ b/lib/pygazelle/inbox.py @@ -58,9 +58,9 @@ class Mailbox(object): """ This class represents the logged in user's inbox/sentbox """ - def __init__(self, parent_api, boxtype='inbox', page='1', sort='unread'): + def __init__(self, parent_api, boxtype='inbox', page='1', sort='unread'): self.parent_api = parent_api - self.boxtype = boxtype + self.boxtype = boxtype self.current_page = page self.total_pages = None self.sort = sort diff --git a/lib/pynma/__init__.py b/lib/pynma/__init__.py index f90424eb..cbd82cfd 100644 --- a/lib/pynma/__init__.py +++ b/lib/pynma/__init__.py @@ -1,4 +1,4 @@ #!/usr/bin/python -from pynma import PyNMA +from pynma import PyNMA diff --git a/lib/pynma/pynma.py b/lib/pynma/pynma.py index fc7d8de2..037145c8 100644 --- a/lib/pynma/pynma.py +++ b/lib/pynma/pynma.py @@ -99,7 +99,7 @@ class PyNMA(object): res = self.callapi('POST', ADD_PATH, datas) results[datas['apikey']] = res return results - + def callapi(self, method, path, args): headers = { 'User-Agent': USER_AGENT } if method == "POST": @@ -116,7 +116,7 @@ class PyNMA(object): 'message': str(e) } pass - + return res def _parse_reponse(self, response): @@ -133,5 +133,5 @@ class PyNMA(object): res['message'] = elem.firstChild.nodeValue res['type'] = elem.tagName return res - - + + diff --git a/lib/requests/cookies.py b/lib/requests/cookies.py index 831c49c6..3456a9fe 100644 --- a/lib/requests/cookies.py +++ b/lib/requests/cookies.py @@ -440,7 +440,7 @@ def merge_cookies(cookiejar, cookies): """ if not isinstance(cookiejar, cookielib.CookieJar): raise ValueError('You can only merge into CookieJar') - + if isinstance(cookies, dict): cookiejar = cookiejar_from_dict( cookies, cookiejar=cookiejar, overwrite=False) diff --git a/lib/requests/packages/chardet/charsetgroupprober.py b/lib/requests/packages/chardet/charsetgroupprober.py index 85e7a1c6..ddbeeea2 100644 --- a/lib/requests/packages/chardet/charsetgroupprober.py +++ b/lib/requests/packages/chardet/charsetgroupprober.py @@ -1,11 +1,11 @@ ######################## BEGIN LICENSE BLOCK ######################## # The Original Code is Mozilla Communicator client code. -# +# # The Initial Developer of the Original Code is # Netscape Communications Corporation. # Portions created by the Initial Developer are Copyright (C) 1998 # the Initial Developer. All Rights Reserved. -# +# # Contributor(s): # Mark Pilgrim - port to Python # @@ -13,12 +13,12 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA diff --git a/lib/requests/packages/chardet/constants.py b/lib/requests/packages/chardet/constants.py index e4d148b3..8895c94e 100644 --- a/lib/requests/packages/chardet/constants.py +++ b/lib/requests/packages/chardet/constants.py @@ -14,12 +14,12 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA diff --git a/lib/requests/packages/chardet/euckrfreq.py b/lib/requests/packages/chardet/euckrfreq.py index a179e4c2..1fa75883 100644 --- a/lib/requests/packages/chardet/euckrfreq.py +++ b/lib/requests/packages/chardet/euckrfreq.py @@ -13,12 +13,12 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA @@ -35,14 +35,14 @@ # # Idea Distribution Ratio = 0.98653 / (1-0.98653) = 73.24 # Random Distribution Ration = 512 / (2350-512) = 0.279. -# -# Typical Distribution Ratio +# +# Typical Distribution Ratio EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0 EUCKR_TABLE_SIZE = 2352 -# Char to FreqOrder table , +# Char to FreqOrder table , EUCKRCharToFreqOrder = ( \ 13, 130, 120,1396, 481,1719,1720, 328, 609, 212,1721, 707, 400, 299,1722, 87, 1397,1723, 104, 536,1117,1203,1724,1267, 685,1268, 508,1725,1726,1727,1728,1398, diff --git a/lib/requests/packages/chardet/euctwprober.py b/lib/requests/packages/chardet/euctwprober.py index fe652fe3..178a3042 100644 --- a/lib/requests/packages/chardet/euctwprober.py +++ b/lib/requests/packages/chardet/euctwprober.py @@ -13,12 +13,12 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA diff --git a/lib/requests/packages/chardet/gb2312prober.py b/lib/requests/packages/chardet/gb2312prober.py index 0325a2d8..b7a181a2 100644 --- a/lib/requests/packages/chardet/gb2312prober.py +++ b/lib/requests/packages/chardet/gb2312prober.py @@ -13,12 +13,12 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA diff --git a/lib/unidecode/__init__.py b/lib/unidecode/__init__.py index 82eb5a3f..dd3d2310 100644 --- a/lib/unidecode/__init__.py +++ b/lib/unidecode/__init__.py @@ -39,7 +39,7 @@ def unidecode(string): if codepoint < 0x80: # Basic ASCII retval.append(str(char)) continue - + if codepoint > 0xeffff: continue # Characters in Private Use Area and above are ignored diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py index 635faac3..abbff97b 100644 --- a/lib/yaml/constructor.py +++ b/lib/yaml/constructor.py @@ -287,7 +287,7 @@ class SafeConstructor(BaseConstructor): return str(value).decode('base64') except (binascii.Error, UnicodeEncodeError), exc: raise ConstructorError(None, None, - "failed to decode base64 data: %s" % exc, node.start_mark) + "failed to decode base64 data: %s" % exc, node.start_mark) timestamp_regexp = re.compile( ur'''^(?P[0-9][0-9][0-9][0-9]) diff --git a/lib/yaml/emitter.py b/lib/yaml/emitter.py index e5bcdccc..488337f2 100644 --- a/lib/yaml/emitter.py +++ b/lib/yaml/emitter.py @@ -674,7 +674,7 @@ class Emitter(object): # Check for indicators. if index == 0: # Leading indicators are special characters. - if ch in u'#,[]{}&*!|>\'\"%@`': + if ch in u'#,[]{}&*!|>\'\"%@`': flow_indicators = True block_indicators = True if ch in u'?:': diff --git a/lib/yaml/parser.py b/lib/yaml/parser.py index f9e3057f..ffec9552 100644 --- a/lib/yaml/parser.py +++ b/lib/yaml/parser.py @@ -482,7 +482,7 @@ class Parser(object): token = self.peek_token() raise ParserError("while parsing a flow sequence", self.marks[-1], "expected ',' or ']', but got %r" % token.id, token.start_mark) - + if self.check_token(KeyToken): token = self.peek_token() event = MappingStartEvent(None, None, True, diff --git a/lib/yaml/scanner.py b/lib/yaml/scanner.py index 5228fad6..4e578581 100644 --- a/lib/yaml/scanner.py +++ b/lib/yaml/scanner.py @@ -314,7 +314,7 @@ class Scanner(object): # Remove the saved possible key position at the current flow level. if self.flow_level in self.possible_simple_keys: key = self.possible_simple_keys[self.flow_level] - + if key.required: raise ScannerError("while scanning a simple key", key.mark, "could not found expected ':'", self.get_mark()) @@ -363,11 +363,11 @@ class Scanner(object): # Read the token. mark = self.get_mark() - + # Add STREAM-START. self.tokens.append(StreamStartToken(mark, mark, encoding=self.encoding)) - + def fetch_stream_end(self): @@ -381,7 +381,7 @@ class Scanner(object): # Read the token. mark = self.get_mark() - + # Add STREAM-END. self.tokens.append(StreamEndToken(mark, mark)) @@ -389,7 +389,7 @@ class Scanner(object): self.done = True def fetch_directive(self): - + # Set the current intendation to -1. self.unwind_indent(-1) @@ -516,7 +516,7 @@ class Scanner(object): self.tokens.append(BlockEntryToken(start_mark, end_mark)) def fetch_key(self): - + # Block context needs additional checks. if not self.flow_level: @@ -566,7 +566,7 @@ class Scanner(object): # It must be a part of a complex key. else: - + # Block context needs additional checks. # (Do we really need them? They will be catched by the parser # anyway.) @@ -1024,14 +1024,14 @@ class Scanner(object): # Unfortunately, folding rules are ambiguous. # # This is the folding according to the specification: - + if folded and line_break == u'\n' \ and leading_non_space and self.peek() not in u' \t': if not breaks: chunks.append(u' ') else: chunks.append(line_break) - + # This is Clark Evans's interpretation (also in the spec # examples): #