Compare commits

...
33 Commits
Author SHA1 Message Date
rembo10 88af84579c Merge branch 'develop' 2014-05-15 21:10:41 -07:00
rembo10 917dc7695f Fix for #1627: adding albums didn't add a ReleaseID to the database 2014-05-15 21:07:04 -07:00
rembo10 65e3e4faa2 Merge remote-tracking branch 'BenMagyar/master' into develop 2014-05-15 19:39:34 -07:00
rembo10 9e7ec3a65c Merge remote-tracking branch 'basilfx/bugfixes5' into develop 2014-05-15 19:36:40 -07:00
rembo10 7ac7b127c6 Merge remote-tracking branch 'AdeHub/ade' into develop 2014-05-15 19:35:26 -07:00
rembo10 3945753c5c Merge remote-tracking branch 'texke/master' into develop 2014-05-15 19:30:28 -07:00
rembo10 599585d57a Merge remote-tracking branch 'delphiactual/develop' into develop 2014-05-15 19:29:18 -07:00
BenM 10cf36401f Changed Add Artist and View MB 2014-05-13 21:11:31 -04:00
Bas Stottelaar fe2834f4a6 Be quiet when running daemon 2014-05-12 12:12:40 +02:00
Bas Stottelaar 674617c0f8 Ensure correct types 2014-05-12 12:09:26 +02:00
Bas Stottelaar f92f8928b0 Remove the validator, since it returns a dict on error and a list on results. Validator always expected a dict 2014-05-12 11:48:57 +02:00
Bas Stottelaar 45868bc523 Toggle VERBOSE at runtime. Just surf to /toggleVerbose. Comes in handy for the ones who don't want to change startup scripts 2014-05-12 11:45:28 +02:00
Bas Stottelaar 9b2594355f Let the --quiet option don't silence --verbose if we want verbose 2014-05-12 11:16:58 +02:00
Bas Stottelaar e30cb26200 Clarified meaning of HTTP error codes. Will show if it's local or remote error 2014-05-12 11:11:57 +02:00
Bas Stottelaar 63d3ab1cc1 Advanced option to turn off remote SSL certificate validation 2014-05-12 11:06:02 +02:00
Bas Stottelaar 188d907e10 GET, POST, HEAD etc are in lower case in requests.py 2014-05-12 11:02:25 +02:00
delphiactual e972a86233 Easier to follow logic for uTorrent 2014-05-11 21:26:58 -06:00
Ade 52a76e87ad Allow search provider names in preferred words
Adds to the priority if the word matches the search provider. The
provider priority is weighted based on position in list.
Preferred words in title take precedence over preferred providers.
Would be great if we could do something like sb, so until then…
2014-05-11 19:35:41 +12:00
Ade 8ab19f8121 remove rutracker track count check
should be able to filter out unwanted entries with Quality and Search
Word settings
2014-05-10 19:06:09 +12:00
Ade 6f3c8c68d4 Lossless min/max filter
filter results if calculated target size outside bitrate range
2014-05-10 18:53:58 +12:00
Ade 98385ff4c8 Transmission change for rutracker
use metainfo for local .torrent files (will only be rutracker)
2014-05-10 09:32:05 +12:00
delphiactual 62a6be26af PyNMA lib 2014-05-07 08:19:08 -06:00
delphiactual 5c1169ba9d Removed leftover DownloadStation [beta] changes 2014-05-06 21:38:28 -06:00
rembo10 0b47a5ca3a Merge remote-tracking branch 'delphiactual/develop' into develop 2014-05-06 19:51:20 -07:00
delphiactual 9da75a777b NMA fix 2014-05-06 20:22:13 -06:00
delphiactual 2fc2190b59 Merge pull request #2 from rembo10/develop
Develop
2014-05-06 19:48:09 -06:00
rembo10 e108935430 Fix for spinner not showing up anymore when marking albums as wanted 2014-05-06 17:01:30 -07:00
Jens Rogier bbde74db8d Fix: "Ignored" albums not ignored during scheduled updates. 2014-05-06 20:41:18 +02:00
rembo10 9fce4edf00 Fix for #1598, everything bring updated, even albums older than pause_delta 2014-05-06 11:21:37 -07:00
rembo10 ea3c6c3278 Changed VERBOSE back to 1 2014-05-06 11:06:31 -07:00
rembo10 6a0bd397cd Merge remote-tracking branch 'delphiactual/develop' into develop
Conflicts:
	headphones/utorrent.py
2014-05-06 11:05:54 -07:00
delphiactual 7bbb137fd9 uTorrent changes... 2014-05-06 02:12:17 -06:00
delphiactual 9c81fbaa2b Merge pull request #1 from rembo10/develop
Develop
2014-05-05 23:23:49 -06:00
19 changed files with 645 additions and 278 deletions
+6 -6
View File
@@ -77,16 +77,16 @@ def main():
args = parser.parse_args() args = parser.parse_args()
if args.verbose: if args.verbose:
headphones.VERBOSE = 2 headphones.VERBOSE = True
elif args.quiet: if args.quiet:
headphones.VERBOSE = 0 headphones.QUIET = True
if args.daemon: if args.daemon:
if sys.platform == 'win32': if sys.platform == 'win32':
print "Daemonize not supported under Windows, starting normally" print "Daemonize not supported under Windows, starting normally"
else: else:
headphones.DAEMON=True headphones.DAEMON = True
headphones.VERBOSE = False headphones.QUIET = True
if args.pidfile: if args.pidfile:
headphones.PIDFILE = str(args.pidfile) headphones.PIDFILE = str(args.pidfile)
@@ -101,7 +101,7 @@ def main():
try: try:
file(headphones.PIDFILE, 'w').write("pid\n") file(headphones.PIDFILE, 'w').write("pid\n")
except IOError, e: except IOError, e:
raise SystemExit("Unable to write PID file: %s [%d]" % (e.strerror, e.errno)) raise SystemExit("Unable to write PID file: %s [%d]", e.strerror, e.errno)
else: else:
logger.warn("Not running in daemon mode. PID file creation disabled.") logger.warn("Not running in daemon mode. PID file creation disabled.")
+4 -4
View File
@@ -240,7 +240,8 @@
refreshTable(); refreshTable();
$('#artistnamelink').text(data["ArtistName"]); $('#artistnamelink').text(data["ArtistName"]);
if (loadingMessage == false){ if (loadingMessage == false){
showMsg("Getting artist information",true); $("#ajaxMsg").after( "<div id='ajaxMsg2' class='ajaxMsg'></div>" );
showArtistMsg("Getting artist information");
loadingMessage = true; loadingMessage = true;
} }
if (spinner_active == false){ if (spinner_active == false){
@@ -255,8 +256,7 @@
else{ else{
$('#artistnamespinner').remove() $('#artistnamespinner').remove()
$('#loadingtext').remove() $('#loadingtext').remove()
$('#ajaxMsg').empty() $('#ajaxMsg2').remove()
$('#ajaxMsg').removeAttr('style')
spinner_active = false spinner_active = false
loadingtext_active = false loadingtext_active = false
loadingMessage = false loadingMessage = false
@@ -303,7 +303,7 @@
resetFilters("albums"); resetFilters("albums");
setTimeout(function(){ setTimeout(function(){
initFancybox(); initFancybox();
},1500) },1500);
} }
$(document).ready(function() { $(document).ready(function() {
+1 -1
View File
@@ -29,7 +29,7 @@
</head> </head>
<body> <body>
<div id="container"> <div id="container">
<div id="ajaxMsg"></div> <div id="ajaxMsg" class="ajaxMsg"></div>
% if not headphones.CURRENT_VERSION: % if not headphones.CURRENT_VERSION:
<div id="updatebar"> <div id="updatebar">
You're running an unknown version of Headphones. <a href="update">Update</a> or You're running an unknown version of Headphones. <a href="update">Update</a> or
+23 -3
View File
@@ -470,7 +470,14 @@
<div class="row radio clearfix"> <div class="row radio clearfix">
<input type="radio" name="preferred_quality" id="preferred_quality0" value="0" ${config['pref_qual_0']}><label>Highest Quality excluding Lossless</label> <input type="radio" name="preferred_quality" id="preferred_quality0" value="0" ${config['pref_qual_0']}><label>Highest Quality excluding Lossless</label>
<input type="radio" name="preferred_quality" id="preferred_quality1" value="1" ${config['pref_qual_1']}><label>Highest Quality including Lossless</label> <input type="radio" name="preferred_quality" id="preferred_quality1" value="1" ${config['pref_qual_1']}><label>Highest Quality including Lossless</label>
<input type="radio" name="preferred_quality" id="preferred_quality3" value="3" ${config['pref_qual_3']}><label>Lossless Only</label> <input type="radio" name="preferred_quality" id="lossless_only" value="3" ${config['pref_qual_3']}><label>Lossless Only</label>
<div id="lossless_only_options">
<span style="padding-left: 20px">
Reject if target size is not in bitrate range: \
<input type="text" class="override-float" name="lossless_bitrate_from" value="${config['lossless_bitrate_from']}" size="4" title="e.g. if album length = 40 mins, from = 350 kbps, then min target size = 102.5 mb, anything less will be rejected">to\
<input type="text" class="override-float" name="lossless_bitrate_to" value="${config['lossless_bitrate_to']}" size="5" title="e.g. if album length = 40 mins, to = 2000 kbps, then max target size = 586 mb, anything greater will be rejected">kbps
</span>
</div>
<input type="radio" id="preferred_bitrate" name="preferred_quality" value="2" ${config['pref_qual_2']}>Preferred Bitrate: <input type="text" class="override-float" name="preferred_bitrate" value="${config['pref_bitrate']}" size="3">kbps<br> <input type="radio" id="preferred_bitrate" name="preferred_quality" value="2" ${config['pref_qual_2']}>Preferred Bitrate: <input type="text" class="override-float" name="preferred_bitrate" value="${config['pref_bitrate']}" size="3">kbps<br>
<div id="preferred_bitrate_options"> <div id="preferred_bitrate_options">
Reject if <strong>less than</strong> <input type="text" class="override-float" name="preferred_bitrate_low_buffer" value="${config['pref_bitrate_low']}" size="3">% or <strong>more than</strong> <input type="text" class="override-float" name="preferred_bitrate_high_buffer" value="${config['pref_bitrate_high']}" size="3">% of the target size (leave blank for no limit)<br><br> Reject if <strong>less than</strong> <input type="text" class="override-float" name="preferred_bitrate_low_buffer" value="${config['pref_bitrate_low']}" size="3">% or <strong>more than</strong> <input type="text" class="override-float" name="preferred_bitrate_high_buffer" value="${config['pref_bitrate_high']}" size="3">% of the target size (leave blank for no limit)<br><br>
@@ -496,7 +503,7 @@
<div class="row"> <div class="row">
<label>Preferred Words</label> <label>Preferred Words</label>
<input type="text" name="preferred_words" value="${config['preferred_words']}" size="50"> <input type="text" name="preferred_words" value="${config['preferred_words']}" size="50">
<small>Results with these words in the title will be preferred over results without them</small> <small>Results with these words in the title will be preferred over results without them (search provider names can also be entered)</small>
</div> </div>
<div class="row"> <div class="row">
<label>Required Words</label> <label>Required Words</label>
@@ -1587,6 +1594,15 @@
} }
}); });
if ($("#lossless_only").is(":checked"))
{
$("#lossless_only_options").show();
}
else
{
$("#lossless_only_options").hide();
}
if ($("#preferred_bitrate").is(":checked")) if ($("#preferred_bitrate").is(":checked"))
{ {
$("#preferred_bitrate_options").show(); $("#preferred_bitrate_options").show();
@@ -1632,17 +1648,21 @@
if ($("#preferred_bitrate").is(":checked")) if ($("#preferred_bitrate").is(":checked"))
{ {
$("#preferred_bitrate_options").slideDown("fast"); $("#preferred_bitrate_options").slideDown("fast");
$("#lossless_only_options").slideUp("fast");
} }
if ($("#preferred_quality0").is(":checked")) if ($("#preferred_quality0").is(":checked"))
{ {
$("#preferred_bitrate_options").slideUp("fast"); $("#preferred_bitrate_options").slideUp("fast");
$("#lossless_only_options").slideUp("fast");
} }
if ($("#preferred_quality1").is(":checked")) if ($("#preferred_quality1").is(":checked"))
{ {
$("#preferred_bitrate_options").slideUp("fast"); $("#preferred_bitrate_options").slideUp("fast");
$("#lossless_only_options").slideUp("fast");
} }
if ($("#preferred_quality3").is(":checked")) if ($("#lossless_only").is(":checked"))
{ {
$("#lossless_only_options").slideDown("fast");
$("#preferred_bitrate_options").slideUp("fast"); $("#preferred_bitrate_options").slideUp("fast");
} }
if ($("#nzb_downloader_sabnzbd").is(":checked")) if ($("#nzb_downloader_sabnzbd").is(":checked"))
+6 -6
View File
@@ -545,7 +545,7 @@ footer {
position: relative; position: relative;
top: 4px; top: 4px;
} }
#ajaxMsg { .ajaxMsg {
border: 1px solid #cccccc; border: 1px solid #cccccc;
background-image: -moz-linear-gradient(#ffffff, #eeeeee) !important; background-image: -moz-linear-gradient(#ffffff, #eeeeee) !important;
background-image: linear-gradient(#ffffff, #eeeeee) !important; background-image: linear-gradient(#ffffff, #eeeeee) !important;
@@ -576,16 +576,16 @@ footer {
-o-opacity: 0.8 !important; -o-opacity: 0.8 !important;
opacity: 0.8 !important; opacity: 0.8 !important;
} }
#ajaxMsg .msg { .ajaxMsg .msg {
font-family: "Trebuchet MS", Helvetica, Arial, sans-serif; font-family: "Trebuchet MS", Helvetica, Arial, sans-serif;
line-height: normal; line-height: normal;
padding-left: 20px; padding-left: 20px;
} }
#ajaxMsg .loader { .ajaxMsg .loader {
position: relative; position: relative;
top: 2px; top: 2px;
} }
#ajaxMsg.success { .ajaxMsg .success {
background-image: -moz-linear-gradient(#d3ffd7, #c2edc6) !important; background-image: -moz-linear-gradient(#d3ffd7, #c2edc6) !important;
background-image: linear-gradient(#d3ffd7, #c2edc6) !important; background-image: linear-gradient(#d3ffd7, #c2edc6) !important;
background-image: -webkit-linear-gradient(#d3ffd7, #c2edc6) !important; background-image: -webkit-linear-gradient(#d3ffd7, #c2edc6) !important;
@@ -595,7 +595,7 @@ footer {
padding: 15px 10px; padding: 15px 10px;
text-align: left; text-align: left;
} }
#ajaxMsg.error { .ajaxMsg .error {
background-image: -moz-linear-gradient(#ffd3d3, #edc4c4) !important; background-image: -moz-linear-gradient(#ffd3d3, #edc4c4) !important;
background-image: linear-gradient(#ffd3d3, #edc4c4) !important; background-image: linear-gradient(#ffd3d3, #edc4c4) !important;
background-image: -webkit-linear-gradient(#ffd3d3, #edc4c4) !important; background-image: -webkit-linear-gradient(#ffd3d3, #edc4c4) !important;
@@ -605,7 +605,7 @@ footer {
padding: 15px 10px; padding: 15px 10px;
text-align: left; text-align: left;
} }
#ajaxMsg .ui-icon { .ajaxMsg .ui-icon {
display: inline-block; display: inline-block;
margin-left: -20px; margin-left: -20px;
top: 2px; top: 2px;
+15
View File
@@ -228,6 +228,21 @@ function showMsg(msg,loader,timeout,ms) {
} }
} }
function showArtistMsg(msg) {
var feedback = $("#ajaxMsg2");
update = $("#updatebar");
if ( update.is(":visible") ) {
var height = update.height() + 35;
feedback.css("bottom",height + "px");
} else {
feedback.removeAttr("style");
}
feedback.fadeIn();
var message = $("<i class='fa fa-refresh fa-spin'></i> " + msg + "</div>");
feedback.css("padding","14px 10px")
$(feedback).prepend(message);
}
function doAjaxCall(url,elem,reload,form) { function doAjaxCall(url,elem,reload,form) {
// Set Message // Set Message
feedback = $("#ajaxMsg"); feedback = $("#ajaxMsg");
+2 -2
View File
@@ -35,12 +35,12 @@
%if type == 'album': %if type == 'album':
<td id="albumname"><a href="${result['albumurl']}">${result['title']}</a></td> <td id="albumname"><a href="${result['albumurl']}">${result['title']}</a></td>
%endif %endif
<td id="artistname"><a href="${result['url']}" title="${result['uniquename']}">${result['uniquename']}</a></td> <td id="artistname"><a href="addArtist?artistid=${result['id']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
<td id="score"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></td> <td id="score"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></td>
%if type == 'album': %if type == 'album':
<td id="add"><a href="addReleaseById?rid=${result['albumid']}"><i class="fa fa-plus"></i> Add this album</a></td> <td id="add"><a href="addReleaseById?rid=${result['albumid']}"><i class="fa fa-plus"></i> Add this album</a></td>
%else: %else:
<td id="add"><a href="addArtist?artistid=${result['id']}"><i class="fa fa-plus"></i> Add this artist</a></td> <td id="add"><a href="${result['url']}"></i> View on MusicBrainz</a></td>
%endif %endif
</tr> </tr>
%endfor %endfor
+21 -8
View File
@@ -38,7 +38,8 @@ SIGNAL = None
SYS_PLATFORM = None SYS_PLATFORM = None
SYS_ENCODING = None SYS_ENCODING = None
VERBOSE = 1 QUIET = False
VERBOSE = False
DAEMON = False DAEMON = False
CREATEPID = False CREATEPID = False
PIDFILE= None PIDFILE= None
@@ -103,6 +104,8 @@ PREFERRED_BITRATE_HIGH_BUFFER = None
PREFERRED_BITRATE_LOW_BUFFER = None PREFERRED_BITRATE_LOW_BUFFER = None
PREFERRED_BITRATE_ALLOW_LOSSLESS = False PREFERRED_BITRATE_ALLOW_LOSSLESS = False
DETECT_BITRATE = False DETECT_BITRATE = False
LOSSLESS_BITRATE_FROM = None
LOSSLESS_BITRATE_TO = None
ADD_ARTISTS = False ADD_ARTISTS = False
CORRECT_METADATA = False CORRECT_METADATA = False
MOVE_FILES = False MOVE_FILES = False
@@ -248,7 +251,7 @@ PLEX_UPDATE = False
PLEX_NOTIFY = False PLEX_NOTIFY = False
NMA_ENABLED = False NMA_ENABLED = False
NMA_APIKEY = None NMA_APIKEY = None
NMA_PRIORITY = None NMA_PRIORITY = 0
NMA_ONSNATCH = None NMA_ONSNATCH = None
PUSHALOT_ENABLED = False PUSHALOT_ENABLED = False
PUSHALOT_APIKEY = None PUSHALOT_APIKEY = None
@@ -292,6 +295,8 @@ JOURNAL_MODE = None
UMASK = None UMASK = None
VERIFY_SSL_CERT = True
def CheckSection(sec): def CheckSection(sec):
""" Check if INI section exists, if not create it """ """ Check if INI section exists, if not create it """
try: try:
@@ -338,7 +343,7 @@ def initialize():
with INIT_LOCK: with INIT_LOCK:
global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \ global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, QUIET, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, GIT_USER, GIT_BRANCH, DO_NOT_OVERRIDE_GIT_BRANCH, \ HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, GIT_USER, GIT_BRANCH, DO_NOT_OVERRIDE_GIT_BRANCH, \
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \ CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \
LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, MOVE_FILES, \ LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, MOVE_FILES, \
@@ -358,9 +363,9 @@ def initialize():
PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \ PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \ MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \ XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \ PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, LOSSLESS_BITRATE_FROM, LOSSLESS_BITRATE_TO, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \ PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, VERIFY_SSL_CERT
if __INITIALIZED__: if __INITIALIZED__:
@@ -438,6 +443,8 @@ def initialize():
PREFERRED_BITRATE_LOW_BUFFER = check_setting_int(CFG, 'General', 'preferred_bitrate_low_buffer', '') PREFERRED_BITRATE_LOW_BUFFER = check_setting_int(CFG, 'General', 'preferred_bitrate_low_buffer', '')
PREFERRED_BITRATE_ALLOW_LOSSLESS = bool(check_setting_int(CFG, 'General', 'preferred_bitrate_allow_lossless', 0)) PREFERRED_BITRATE_ALLOW_LOSSLESS = bool(check_setting_int(CFG, 'General', 'preferred_bitrate_allow_lossless', 0))
DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0)) DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0))
LOSSLESS_BITRATE_FROM = check_setting_int(CFG, 'General', 'lossless_bitrate_from', '')
LOSSLESS_BITRATE_TO = check_setting_int(CFG, 'General', 'lossless_bitrate_to', '')
ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1)) ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1))
CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0)) CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0))
MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0)) MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0))
@@ -643,6 +650,8 @@ def initialize():
ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80) ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80)
VERIFY_SSL_CERT = bool(check_setting_int(CFG, 'Advanced', 'verify_ssl_cert', 1))
# update folder formats in the config & bump up config version # update folder formats in the config & bump up config version
if CONFIG_VERSION == '0': if CONFIG_VERSION == '0':
from headphones.helpers import replace_all from headphones.helpers import replace_all
@@ -713,8 +722,9 @@ def initialize():
if VERBOSE: if VERBOSE:
sys.stderr.write('Unable to create the log directory. Logging to screen only.\n') sys.stderr.write('Unable to create the log directory. Logging to screen only.\n')
# Start the logger, silence console logging if we need to # Start the logger, disable console if needed
logger.initLogger(verbose=VERBOSE) logger.initLogger(console=not QUIET, verbose=VERBOSE)
logger.initLogger(console=not QUIET, verbose=False)
if not CACHE_DIR: if not CACHE_DIR:
# Put the cache dir in the data dir for now # Put the cache dir in the data dir for now
@@ -862,6 +872,8 @@ def config_write():
new_config['General']['preferred_bitrate_low_buffer'] = PREFERRED_BITRATE_LOW_BUFFER new_config['General']['preferred_bitrate_low_buffer'] = PREFERRED_BITRATE_LOW_BUFFER
new_config['General']['preferred_bitrate_allow_lossless'] = int(PREFERRED_BITRATE_ALLOW_LOSSLESS) new_config['General']['preferred_bitrate_allow_lossless'] = int(PREFERRED_BITRATE_ALLOW_LOSSLESS)
new_config['General']['detect_bitrate'] = int(DETECT_BITRATE) new_config['General']['detect_bitrate'] = int(DETECT_BITRATE)
new_config['General']['lossless_bitrate_from'] = LOSSLESS_BITRATE_FROM
new_config['General']['lossless_bitrate_to'] = LOSSLESS_BITRATE_TO
new_config['General']['auto_add_artists'] = int(ADD_ARTISTS) new_config['General']['auto_add_artists'] = int(ADD_ARTISTS)
new_config['General']['correct_metadata'] = int(CORRECT_METADATA) new_config['General']['correct_metadata'] = int(CORRECT_METADATA)
new_config['General']['move_files'] = int(MOVE_FILES) new_config['General']['move_files'] = int(MOVE_FILES)
@@ -1014,7 +1026,7 @@ def config_write():
new_config['NMA'] = {} new_config['NMA'] = {}
new_config['NMA']['nma_enabled'] = int(NMA_ENABLED) new_config['NMA']['nma_enabled'] = int(NMA_ENABLED)
new_config['NMA']['nma_apikey'] = NMA_APIKEY new_config['NMA']['nma_apikey'] = NMA_APIKEY
new_config['NMA']['nma_priority'] = NMA_PRIORITY new_config['NMA']['nma_priority'] = int(NMA_PRIORITY)
new_config['NMA']['nma_onsnatch'] = int(NMA_ONSNATCH) new_config['NMA']['nma_onsnatch'] = int(NMA_ONSNATCH)
new_config['Pushalot'] = {} new_config['Pushalot'] = {}
@@ -1092,6 +1104,7 @@ def config_write():
new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT
new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB
new_config['Advanced']['journal_mode'] = JOURNAL_MODE new_config['Advanced']['journal_mode'] = JOURNAL_MODE
new_config['Advanced']['verify_ssl_cert'] = int(VERIFY_SSL_CERT)
new_config.write() new_config.write()
+20 -12
View File
@@ -231,22 +231,32 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
if not forcefull: if not forcefull:
new_release_group = False
try: try:
check_release_date = rg_exists['ReleaseDate'] check_release_date = rg_exists['ReleaseDate']
except TypeError: except TypeError:
check_release_date = None check_release_date = None
new_release_group = True
if check_release_date:
if check_release_date[0] is None: 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'])) logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title']))
new_releases = mb.get_new_releases(rgid,includeExtras,True) new_releases = mb.get_new_releases(rgid,includeExtras,True)
else: else:
if len(check_release_date[0]) == 10: if len(check_release_date) == 10:
release_date = check_release_date[0] release_date = check_release_date
elif len(check_release_date[0]) == 7: elif len(check_release_date) == 7:
release_date = check_release_date[0]+"-31" release_date = check_release_date+"-31"
elif len(check_release_date[0]) == 4: elif len(check_release_date) == 4:
release_date = check_release_date[0]+"-12-31" release_date = check_release_date+"-12-31"
else: else:
release_date = today release_date = today
if helpers.get_age(today) - helpers.get_age(release_date) < pause_delta: if helpers.get_age(today) - helpers.get_age(release_date) < pause_delta:
@@ -256,9 +266,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
logger.info("[%s] Skipping: %s (Release Date >%s Days)" % (artist['artist_name'], rg['title'], pause_delta)) logger.info("[%s] Skipping: %s (Release Date >%s Days)" % (artist['artist_name'], rg['title'], pause_delta))
skip_log = 1 skip_log = 1
new_releases = 0 new_releases = 0
else:
logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title']))
new_releases = mb.get_new_releases(rgid,includeExtras)
if force_repackage == 1: if force_repackage == 1:
new_releases = -1 new_releases = -1
@@ -496,7 +503,7 @@ def finalize_update(artistid, artistname, errors=False):
myDB = db.DBConnection() myDB = db.DBConnection()
latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone() latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone()
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [artistid])) totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [artistid]))
#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 ?', [artist['artist_name']])) #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 ?', [artist['artist_name']]))
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])) 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]))
@@ -582,6 +589,7 @@ def addReleaseById(rid):
controlValueDict = {"AlbumID": rgid} controlValueDict = {"AlbumID": rgid}
newValueDict = {"ArtistID": release_dict['artist_id'], newValueDict = {"ArtistID": release_dict['artist_id'],
"ReleaseID": rgid,
"ArtistName": release_dict['artist_name'], "ArtistName": release_dict['artist_name'],
"AlbumTitle": release_dict['rg_title'], "AlbumTitle": release_dict['rg_title'],
"AlbumASIN": release_dict['asin'], "AlbumASIN": release_dict['asin'],
+17 -4
View File
@@ -43,19 +43,32 @@ class LogListHandler(logging.Handler):
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName)) headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
def initLogger(verbose=1): def initLogger(console=False, verbose=False):
""" """
Setup logging for Headphones. It uses the logger instance with the name Setup logging for Headphones. It uses the logger instance with the name
'headphones'. Three log handlers are added: 'headphones'. Three log handlers are added:
* RotatingFileHandler: for the file headphones.log * RotatingFileHandler: for the file headphones.log
* LogListHandler: for Web UI * LogListHandler: for Web UI
* StreamHandler: for console (if verbose > 0) * StreamHandler: for console (if console)
Console logging is only enabled if console is set to True.
""" """
# Close and remove old handlers. This is required to reinit the loggers
# at runtime
for handler in logger.handlers[:]:
# Just make sure it is cleaned up.
if isinstance(handler, handlers.RotatingFileHandler):
handler.close()
elif isinstance(handler, logging.StreamHandler):
handler.flush()
logger.removeHandler(handler)
# Configure the logger to accept all messages # Configure the logger to accept all messages
logger.propagate = False logger.propagate = False
logger.setLevel(logging.DEBUG if verbose == 2 else logging.INFO) logger.setLevel(logging.DEBUG if verbose else logging.INFO)
# Setup file logger # Setup file logger
filename = os.path.join(headphones.LOG_DIR, FILENAME) filename = os.path.join(headphones.LOG_DIR, FILENAME)
@@ -74,7 +87,7 @@ def initLogger(verbose=1):
logger.addHandler(loglist_handler) logger.addHandler(loglist_handler)
# Setup console logger # Setup console logger
if verbose: if console:
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S') console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
console_handler = logging.StreamHandler() console_handler = logging.StreamHandler()
console_handler.setFormatter(console_formatter) console_handler.setFormatter(console_formatter)
+28 -22
View File
@@ -27,7 +27,7 @@ import time
from xml.dom import minidom from xml.dom import minidom
from httplib import HTTPSConnection from httplib import HTTPSConnection
from urllib import urlencode from urllib import urlencode
from lib.pynma import pynma
import lib.oauth2 as oauth import lib.oauth2 as oauth
import lib.pythontwitter as twitter import lib.pythontwitter as twitter
@@ -380,34 +380,40 @@ class Plex:
logger.warn('Error sending notification request to Plex Media Server') logger.warn('Error sending notification request to Plex Media Server')
class NMA: class NMA:
def notify(self, artist=None, album=None, snatched=None):
title = 'Headphones'
api = headphones.NMA_APIKEY
nma_priority = headphones.NMA_PRIORITY
def __init__(self): logger.debug(u"NMA title: " + title)
logger.debug(u"NMA API: " + api)
logger.debug(u"NMA Priority: " + str(nma_priority))
self.apikey = headphones.NMA_APIKEY if snatched:
self.priority = headphones.NMA_PRIORITY event = snatched + " snatched!"
message = "Headphones has snatched: " + snatched
def _send(self, data):
return request.request_content('https://www.notifymyandroid.com/publicapi/notify', data=data)
def notify(self, artist=None, album=None, snatched_nzb=None):
apikey = self.apikey
priority = self.priority
if snatched_nzb:
event = snatched_nzb + " snatched!"
description = "Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+"
else: else:
event = artist + ' - ' + album + ' complete!' event = artist + ' - ' + album + ' complete!'
description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']' message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
data = { 'apikey': apikey, 'application':'Headphones', 'event': event, 'description': description, 'priority': priority} logger.debug(u"NMA event: " + event)
logger.debug(u"NMA message: " + message)
logger.info('Sending notification request to NotifyMyAndroid') batch = False
request = self._send(data)
if not request: p = pynma.PyNMA()
logger.warn('Error sending notification request to NotifyMyAndroid') keys = api.split(',')
p.addkey(keys)
if len(keys) > 1: batch = True
response = p.push(title, event, message, priority=nma_priority, batch_mode=batch)
if not response[api][u'code'] == u'200':
logger.error(u'Could not send notification to NotifyMyAndroid')
return False
else:
return True
class PUSHBULLET: class PUSHBULLET:
+15 -2
View File
@@ -5,6 +5,7 @@ from bs4 import BeautifulSoup
import requests import requests
import feedparser import feedparser
import headphones
def request_response(url, method="get", auto_raise=True, whitelist_status_code=None, **kwargs): def request_response(url, method="get", auto_raise=True, whitelist_status_code=None, **kwargs):
""" """
@@ -17,9 +18,13 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
if whitelist_status_code and type(whitelist_status_code) != list: if whitelist_status_code and type(whitelist_status_code) != list:
whitelist_status_code = [whitelist_status_code] whitelist_status_code = [whitelist_status_code]
# Disable verification of SSL certificates if requested. Note: this could
# pose a security issue!
kwargs["verify"] = headphones.VERIFY_SSL_CERT
# Map method to the request.XXX method. This is a simple hack, but it allows # Map method to the request.XXX method. This is a simple hack, but it allows
# requests to apply more magic per method. See lib/requests/api.py. # requests to apply more magic per method. See lib/requests/api.py.
request_method = getattr(requests, method) request_method = getattr(requests, method.lower())
try: try:
# Request the URL # Request the URL
@@ -45,7 +50,15 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
logger.error("Request timed out.") logger.error("Request timed out.")
except requests.HTTPError, e: except requests.HTTPError, e:
if e.response is not None: if e.response is not None:
logger.error("Request raise HTTP error with status code: %d", e.response.status_code) if e.response.status_code >= 500:
cause = "remote server error"
elif e.response.status_code >= 400:
cause = "local request error"
else:
# I don't think we will end up here, but for completeness
cause = "unknown"
logger.error("Request raise HTTP error with status code %d (%s).", e.response.status_code, cause)
else: else:
logger.error("Request raised HTTP error.") logger.error("Request raised HTTP error.")
except requests.RequestException, e: except requests.RequestException, e:
+70 -13
View File
@@ -21,6 +21,8 @@ from lib.pygazelle import encoding as gazelleencoding
from lib.pygazelle import format as gazelleformat from lib.pygazelle import format as gazelleformat
from lib.pygazelle import media as gazellemedia from lib.pygazelle import media as gazellemedia
from xml.dom import minidom from xml.dom import minidom
from base64 import b16encode, b32decode
from hashlib import sha1
import os, re, time import os, re, time
import string import string
@@ -28,12 +30,13 @@ import shutil
import requests import requests
import subprocess import subprocess
import headphones import headphones
from headphones.common import USER_AGENT from headphones.common import USER_AGENT
from headphones import logger, db, helpers, classes, sab, nzbget, request from headphones import logger, db, helpers, classes, sab, nzbget, request
from headphones import utorrent, transmission, notifiers from headphones import utorrent, transmission, notifiers
import lib.bencode as bencode from lib.bencode import bencode as bencode, bdecode
import headphones.searcher_rutracker as rutrackersearch import headphones.searcher_rutracker as rutrackersearch
rutracker = rutrackersearch.Rutracker() rutracker = rutrackersearch.Rutracker()
@@ -148,11 +151,20 @@ def sort_search_results(resultlist, album, new):
# Add a priority if it has any of the preferred words # Add a priority if it has any of the preferred words
temp_list = [] temp_list = []
preferred_words = None
if headphones.PREFERRED_WORDS:
preferred_words = helpers.split_string(headphones.PREFERRED_WORDS)
for result in resultlist: for result in resultlist:
if headphones.PREFERRED_WORDS and any(word.lower() in result[0].lower() for word in helpers.split_string(headphones.PREFERRED_WORDS)): priority = 0
temp_list.append((result[0],result[1],result[2],result[3],result[4],1)) if preferred_words:
else: if any(word.lower() in result[0].lower() for word in preferred_words):
temp_list.append((result[0],result[1],result[2],result[3],result[4],0)) priority = 1
# add a search provider priority (weighted based on position)
i = next((i for i, word in enumerate(preferred_words) if word in result[3].lower()), None)
if i != None:
priority += round((len(preferred_words) - i) / float(len(preferred_words)),2)
temp_list.append((result[0],result[1],result[2],result[3],result[4],priority))
resultlist = temp_list resultlist = temp_list
@@ -215,6 +227,34 @@ def sort_search_results(resultlist, album, new):
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True) finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
# lossless - ignore results if target size outside bitrate range
elif headphones.PREFERRED_QUALITY == 3 and (headphones.LOSSLESS_BITRATE_FROM or headphones.LOSSLESS_BITRATE_TO):
finallist = []
tracks = myDB.select('SELECT TrackDuration from tracks WHERE AlbumID=?', [album['AlbumID']])
if len(tracks):
albumlength = sum([pair[0] for pair in tracks])
mintargetsize = 0
maxtargetsize = 0
if headphones.LOSSLESS_BITRATE_FROM:
mintargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_FROM) * 128
if headphones.LOSSLESS_BITRATE_TO:
maxtargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_TO) * 128
if mintargetsize > 0 or maxtargetsize > 0:
for i, result in reversed(list(enumerate(resultlist))):
if int(result[1]) < mintargetsize and mintargetsize > 0 or int(result[1]) > maxtargetsize and maxtargetsize > 0:
if int(result[1]) < mintargetsize:
logger.info("%s is too small for this album - not considering it. (Size: %s, Minsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(mintargetsize))
else:
logger.info("%s is too large for this album - not considering it. (Size: %s, Maxsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(maxtargetsize))
del resultlist[i]
if len (resultlist):
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
else: else:
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True) finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
@@ -534,8 +574,7 @@ def searchNZB(album, new=False, losslessOnly=False):
data = request.request_json( data = request.request_json(
url='http://api.omgwtfnzbs.org/json/', url='http://api.omgwtfnzbs.org/json/',
params=params, headers=headers, params=params, headers=headers
validator=lambda x: type(x) == dict
) )
# Parse response # Parse response
@@ -659,7 +698,7 @@ def send_to_downloader(data, bestqual, album):
#Open the fresh torrent file again so we can extract the proper torrent name #Open the fresh torrent file again so we can extract the proper torrent name
#Used later in post-processing. #Used later in post-processing.
with open(download_path, 'rb') as fp: with open(download_path, 'rb') as fp:
torrent_info = bencode.bdecode(fp.read()) torrent_info = bdecode(fp.read())
folder_name = torrent_info['info'].get('name', '') folder_name = torrent_info['info'].get('name', '')
logger.info('Torrent folder name: %s' % folder_name) logger.info('Torrent folder name: %s' % folder_name)
@@ -696,7 +735,7 @@ def send_to_downloader(data, bestqual, album):
except Exception, e: except Exception, e:
logger.exception("Unhandled exception") logger.exception("Unhandled exception")
else: else:# if headphones.TORRENT_DOWNLOADER == 2:
logger.info("Sending torrent to uTorrent") logger.info("Sending torrent to uTorrent")
# rutracker needs cookies to be set, pass the .torrent file instead of url # rutracker needs cookies to be set, pass the .torrent file instead of url
@@ -705,7 +744,9 @@ def send_to_downloader(data, bestqual, album):
else: else:
file_or_url = bestqual[2] file_or_url = bestqual[2]
folder_name = utorrent.addTorrent(file_or_url) _hash = CalculateTorrentHash(file_or_url, data)
folder_name = utorrent.addTorrent(file_or_url, _hash)
if folder_name: if folder_name:
logger.info('Torrent folder name: %s' % folder_name) logger.info('Torrent folder name: %s' % folder_name)
@@ -757,7 +798,7 @@ def send_to_downloader(data, bestqual, album):
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH: if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
logger.info(u"Sending NMA notification") logger.info(u"Sending NMA notification")
nma = notifiers.NMA() nma = notifiers.NMA()
nma.notify(snatched_nzb=name) nma.notify(snatched=name)
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH: if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
logger.info(u"Sending Pushalot notification") logger.info(u"Sending Pushalot notification")
pushalot = notifiers.PUSHALOT() pushalot = notifiers.PUSHALOT()
@@ -1400,8 +1441,8 @@ def preprocess(resultlist):
for result in resultlist: for result in resultlist:
if result[4] == 'torrent': if result[4] == 'torrent':
#Get out of here if we're using Transmission or uTorrent #Get out of here if we're using Transmission
if headphones.TORRENT_DOWNLOADER != 0: if headphones.TORRENT_DOWNLOADER == 1: ## if not a magnet link still need the .torrent to generate hash... uTorrent support labeling
return True, result return True, result
# get outta here if rutracker # get outta here if rutracker
if result[3] == 'rutracker.org': if result[3] == 'rutracker.org':
@@ -1451,3 +1492,19 @@ def preprocess(resultlist):
continue continue
return (None, None) return (None, None)
def CalculateTorrentHash(link, data):
if link.startswith('magnet'):
tor_hash = re.findall('urn:btih:([\w]{32,40})', link)[0]
if len(tor_hash) == 32:
tor_hash = b16encode(b32decode(tor_hash)).lower()
else:
info = bdecode(data)["info"]
tor_hash = sha1(bencode(info)).hexdigest()
logger.debug('Torrent Hash: ' + str(tor_hash))
return tor_hash
+17 -23
View File
@@ -18,6 +18,7 @@ from tempfile import mkdtemp
class Rutracker(): class Rutracker():
logged_in = False logged_in = False
# Stores a number of login attempts to prevent recursion. # Stores a number of login attempts to prevent recursion.
#login_counter = 0 #login_counter = 0
@@ -51,7 +52,6 @@ class Rutracker():
pass pass
# Check if we're logged in # Check if we're logged in
for cookie in self.cookiejar: for cookie in self.cookiejar:
if cookie.name == 'bb_data': if cookie.name == 'bb_data':
self.logged_in = True self.logged_in = True
@@ -64,7 +64,6 @@ class Rutracker():
""" """
# Build search url # Build search url
searchterm = '' searchterm = ''
if artist != 'Various Artists': if artist != 'Various Artists':
searchterm = artist searchterm = artist
@@ -83,7 +82,6 @@ class Rutracker():
format = '+mp3||aac' format = '+mp3||aac'
# sort by size, descending. # sort by size, descending.
sort = '&o=7&s=2' sort = '&o=7&s=2'
searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort) searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort)
@@ -111,25 +109,21 @@ class Rutracker():
#logger.debug (soup.prettify()) #logger.debug (soup.prettify())
# Title # Title
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}): for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
title = link.get_text() title = link.get_text()
titles.append(title) titles.append(title)
# Download URL # Download URL
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}): for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
url = link.get('href') url = link.get('href')
urls.append(url) urls.append(url)
# Seeders # Seeders
for link in soup.find_all('b', attrs={'class' : 'seedmed'}): for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
seeder = link.get_text() seeder = link.get_text()
seeders.append(seeder) seeders.append(seeder)
# Size # Size
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}): for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
size = link.u.string size = link.u.string
sizes.append(size) sizes.append(size)
@@ -138,16 +132,19 @@ class Rutracker():
pass pass
# Combine lists # Combine lists
torrentlist = zip(titles, urls, seeders, sizes) torrentlist = zip(titles, urls, seeders, sizes)
# return if nothing found # return if nothing found
if not torrentlist: if not torrentlist:
return False return False
# get headphones track count for album, return if not found # don't bother checking track counts anymore, let searcher filter instead
# leave code in just in case
check_track_count = False
if check_track_count:
# get headphones track count for album, return if not found
myDB = db.DBConnection() myDB = db.DBConnection()
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid]) tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
hptrackcount = len(tracks) hptrackcount = len(tracks)
@@ -170,13 +167,18 @@ class Rutracker():
seeders = torrent[2] seeders = torrent[2]
size = torrent[3] size = torrent[3]
title = returntitle.lower()
if int(size) <= maxsize and int(seeders) >= minseeders: if int(size) <= maxsize and int(seeders) >= minseeders:
# Check torrent info #Torrent topic page
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t'] torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
# add to list
if not check_track_count:
valid = True
else:
# Check torrent info
self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)) self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
# Debug # Debug
@@ -195,7 +197,6 @@ class Rutracker():
return False return False
# get torrent track count and check for cue # get torrent track count and check for cue
trackcount = 0 trackcount = 0
cuecount = 0 cuecount = 0
@@ -208,9 +209,7 @@ class Rutracker():
if '.cue' in file: if '.cue' in file:
cuecount += 1 cuecount += 1
#Torrent topic page title = returntitle.lower()
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
logger.debug ('torrent title: %s' % title) logger.debug ('torrent title: %s' % title)
logger.debug ('headphones trackcount: %s' % hptrackcount) logger.debug ('headphones trackcount: %s' % hptrackcount)
logger.debug ('rutracker trackcount: %s' % trackcount) logger.debug ('rutracker trackcount: %s' % trackcount)
@@ -218,7 +217,6 @@ class Rutracker():
# If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s) # If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
# This is for the case where we have a single .flac/.wav which can be split by cue # This is for the case where we have a single .flac/.wav which can be split by cue
# Not great, but shouldn't be doing this too often # Not great, but shouldn't be doing this too often
totallogcount = 0 totallogcount = 0
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount: if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
page = self.opener.open(topicurl, timeout=60) page = self.opener.open(topicurl, timeout=60)
@@ -247,7 +245,6 @@ class Rutracker():
# If torrent track count = hp track count then return torrent, # If torrent track count = hp track count then return torrent,
# if greater, check for deluxe/special/foreign editions # if greater, check for deluxe/special/foreign editions
# if less, then allow if it's a single track with a cue # if less, then allow if it's a single track with a cue
valid = False valid = False
if trackcount == hptrackcount: if trackcount == hptrackcount:
@@ -257,17 +254,14 @@ class Rutracker():
valid = True valid = True
# Add to list # Add to list
if valid: if valid:
rulist.append((returntitle, size, topicurl)) rulist.append((returntitle, size, topicurl))
else: else:
if topicurl: if topicurl:
logger.info(u'<a href="%s">Torrent</a> found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount)) logger.info(u'<a href="%s">Torrent</a> found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount))
else: else:
logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders))) logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders)))
return rulist return rulist
def get_torrent(self, url, savelocation=None): def get_torrent(self, url, savelocation=None):
+7
View File
@@ -30,6 +30,13 @@ from headphones import logger, notifiers, request
def addTorrent(link): def addTorrent(link):
method = 'torrent-add' method = 'torrent-add'
if link.endswith('.torrent'):
f = open(link,'rb')
metainfo = str(base64.b64encode(f.read()))
f.close()
arguments = {'metainfo': metainfo, 'download-dir':headphones.DOWNLOAD_TORRENT_DIR}
else:
arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR} arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
response = torrentAction(method,arguments) response = torrentAction(method,arguments)
+128 -60
View File
@@ -13,31 +13,19 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>. # along with Headphones. If not, see <http://www.gnu.org/licenses/>.
import re import urllib, urllib2, urlparse, cookielib
import os import json, re, os, time
import time
import base64
import headphones import headphones
import simplejson as json from headphones import logger
from headphones import logger, notifiers, request class utorrentclient(object):
TOKEN_REGEX = "<div id='token' style='display:none;'>([^<>]+)</div>"
# This is just a simple script to send torrents to transmission. The def __init__(self, base_url = None, username = None, password = None,):
# intention is to turn this into a class where we can check the state
# of the download, set the download dir, etc.
# TODO: Store the session id so we don't need to make 2 calls
# Store torrent id so we can check up on it
def addTorrent(link):
host = headphones.UTORRENT_HOST host = headphones.UTORRENT_HOST
username = headphones.UTORRENT_USERNAME
password = headphones.UTORRENT_PASSWORD
label = headphones.UTORRENT_LABEL
token = ''
if not host.startswith('http'): if not host.startswith('http'):
host = 'http://' + host host = 'http://' + host
@@ -45,54 +33,134 @@ def addTorrent(link):
host = host[:-1] host = host[:-1]
if host.endswith('/gui'): if host.endswith('/gui'):
host = host + '/' host = host[:-4]
else:
host = host + '/gui/'
# Retrieve session id self.base_url = host
auth = (username, password) if username and password else None self.username = headphones.UTORRENT_USERNAME
token_request = request.request_response(host + 'token.html', auth=auth) self.password = headphones.UTORRENT_PASSWORD
self.opener = self._make_opener('uTorrent', self.base_url, self.username, self.password)
self.token = self._get_token()
#TODO refresh token, when necessary
token = re.findall('<div.*?>(.*?)</', token_request.content)[0] def _make_opener(self, realm, base_url, username, password):
guid = token_request.cookies['GUID'] """uTorrent API need HTTP Basic Auth and cookie support for token verify."""
auth = urllib2.HTTPBasicAuthHandler()
auth.add_password(realm=realm,uri=base_url,user=username,passwd=password)
opener = urllib2.build_opener(auth)
urllib2.install_opener(opener)
cookies = dict(GUID = guid) cookie_jar = cookielib.CookieJar()
cookie_handler = urllib2.HTTPCookieProcessor(cookie_jar)
if link.startswith("magnet") or link.startswith("http") or link.endswith(".torrent"): handlers = [auth, cookie_handler]
params = {'action':'add-url', 's':link, 'token':token} opener = urllib2.build_opener(*handlers)
response = request.request_json(host, params=params, auth=auth, cookies=cookies) return opener
else:
params = {'action':'add-file', 'token':token}
files = {'torrent_file':{'music.torrent' : link}}
response = request.request_json(host, method="post", params=params, files=files, auth=auth, cookies=cookies)
if not response:
logger.error("Error sending torrent to uTorrent")
return
if link.startswith('magnet'): def _get_token(self):
tor_hash = re.findall('urn:btih:([\w]{32,40})', link)[0] url = urlparse.urljoin(self.base_url, 'gui/token.html')
if len(tor_hash) == 32: try:
tor_hash = b16encode(b32decode(tor_hash)).lower() response = self.opener.open(url)
else: except urllib2.HTTPError as err:
info = bdecode(link.content)["info"] logger.debug('URL: ' + str(url))
tor_hash = sha1(bencode(info)).hexdigest() logger.debug('Error getting Token. uTorrent responded with error: ' + str(err))
match = re.search(utorrentclient.TOKEN_REGEX, response.read())
return match.group(1)
params = {'action':'setprops', 'hash':tor_hash,'s':'label', 'v':label, 'token':token} def list(self, **kwargs):
response = request.request_json(host, params=params, auth=auth, cookies=cookies) params = [('list', '1')]
if not response: params += kwargs.items()
logger.error("Error setting torrent label in uTorrent") return self._action(params)
return
# folder info can probably be cleaned up with getprops def add_url(self, url):
folder = None #can receive magnet or normal .torrent link
params = [('action', 'add-url'), ('s', url)]
return self._action(params)
params = {'list':'1', 'token':token} def start(self, *hashes):
response = request.request_json(host, params=params, auth=auth, cookies=cookies) params = [('action', 'start'), ]
if not response: for hash in hashes:
logger.error("Error getting torrent information from uTorrent") params.append(('hash', hash))
return return self._action(params)
for torrent in response['torrents']: def stop(self, *hashes):
folder = os.path.basename(torrent[26]) params = [('action', 'stop'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
return folder def pause(self, *hashes):
params = [('action', 'pause'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def forcestart(self, *hashes):
params = [('action', 'forcestart'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def getfiles(self, hash):
params = [('action', 'getfiles'), ('hash', hash)]
return self._action(params)
def getprops(self, hash):
params = [('action', 'getprops'), ('hash', hash)]
return self._action(params)
def setprops(self, hash, s, v):
params = [('action', 'setprops'), ('hash', hash), ("s", s), ("v", v)]
return self._action(params)
def setprio(self, hash, priority, *files):
params = [('action', 'setprio'), ('hash', hash), ('p', str(priority))]
for file_index in files:
params.append(('f', str(file_index)))
return self._action(params)
def _action(self, params, body=None, content_type=None):
url = self.base_url + '/gui/' + '?token=' + self.token + '&' + urllib.urlencode(params)
request = urllib2.Request(url)
if body:
request.add_data(body)
request.add_header('Content-length', len(body))
if content_type:
request.add_header('Content-type', content_type)
try:
response = self.opener.open(request)
return response.code, json.loads(response.read())
except urllib2.HTTPError as err:
logger.debug('URL: ' + str(url))
logger.debug('uTorrent webUI raised the following error: ' + str(err))
def labelTorrent(hash):
label = headphones.UTORRENT_LABEL
uTorrentClient = utorrentclient()
settinglabel = True
while settinglabel:
torrentList = uTorrentClient.list()
for torrent in torrentList[1].get('torrents'):
if (torrent[0].lower() == hash):
uTorrentClient.setprops(hash,'label',label)
settinglabel = False
return True
def dirTorrent(hash):
uTorrentClient = utorrentclient()
torrentList = uTorrentClient.list()
for torrent in torrentList[1].get('torrents'):
if (torrent[0].lower() == hash):
return torrent[26]
return False
def addTorrent(link, hash):
uTorrentClient = utorrentclient()
uTorrentClient.add_url(link)
labelTorrent(hash)
return dirTorrent(hash)
+14 -2
View File
@@ -736,6 +736,14 @@ class WebInterface(object):
raise cherrypy.HTTPRedirect("logs") raise cherrypy.HTTPRedirect("logs")
clearLogs.exposed = True clearLogs.exposed = True
def toggleVerbose(self):
headphones.VERBOSE = not headphones.VERBOSE
logger.initLogger(not headphones.QUIET, headphones.VERBOSE)
logger.info("Verbose toggled, set to %s", headphones.VERBOSE)
logger.debug("If you read this message, debug logging is available")
raise cherrypy.HTTPRedirect("logs")
toggleVerbose.exposed = True
def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs): def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs):
iDisplayStart = int(iDisplayStart) iDisplayStart = int(iDisplayStart)
@@ -1000,6 +1008,8 @@ class WebInterface(object):
"pref_bitrate_low" : headphones.PREFERRED_BITRATE_LOW_BUFFER, "pref_bitrate_low" : headphones.PREFERRED_BITRATE_LOW_BUFFER,
"pref_bitrate_allow_lossless" : checked(headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS), "pref_bitrate_allow_lossless" : checked(headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS),
"detect_bitrate" : checked(headphones.DETECT_BITRATE), "detect_bitrate" : checked(headphones.DETECT_BITRATE),
"lossless_bitrate_from" : headphones.LOSSLESS_BITRATE_FROM,
"lossless_bitrate_to" : headphones.LOSSLESS_BITRATE_TO,
"move_files" : checked(headphones.MOVE_FILES), "move_files" : checked(headphones.MOVE_FILES),
"rename_files" : checked(headphones.RENAME_FILES), "rename_files" : checked(headphones.RENAME_FILES),
"correct_metadata" : checked(headphones.CORRECT_METADATA), "correct_metadata" : checked(headphones.CORRECT_METADATA),
@@ -1136,8 +1146,8 @@ class WebInterface(object):
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None, xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None,
pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0, pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0,
osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None, osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None,
preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None, file_permissions=None, folder_permissions=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, lossless_bitrate_from=None, lossless_bitrate_to=None, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None,
plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0, file_permissions=None, folder_permissions=None, plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, mpc_enabled=False, **kwargs ): songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, mpc_enabled=False, **kwargs ):
headphones.HTTP_HOST = http_host headphones.HTTP_HOST = http_host
@@ -1216,6 +1226,8 @@ class WebInterface(object):
headphones.PREFERRED_BITRATE_LOW_BUFFER = preferred_bitrate_low_buffer headphones.PREFERRED_BITRATE_LOW_BUFFER = preferred_bitrate_low_buffer
headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS = preferred_bitrate_allow_lossless headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS = preferred_bitrate_allow_lossless
headphones.DETECT_BITRATE = detect_bitrate headphones.DETECT_BITRATE = detect_bitrate
headphones.LOSSLESS_BITRATE_FROM = lossless_bitrate_from
headphones.LOSSLESS_BITRATE_TO = lossless_bitrate_to
headphones.MOVE_FILES = move_files headphones.MOVE_FILES = move_files
headphones.CORRECT_METADATA = correct_metadata headphones.CORRECT_METADATA = correct_metadata
headphones.RENAME_FILES = rename_files headphones.RENAME_FILES = rename_files
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/python
from pynma import PyNMA
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/python
from xml.dom.minidom import parseString
from httplib import HTTPSConnection
from urllib import urlencode
__version__ = "0.1"
API_SERVER = 'nma.usk.bz'
ADD_PATH = '/publicapi/notify'
USER_AGENT="PyNMA/v%s"%__version__
def uniq_preserve(seq): # Dave Kirby
# Order preserving
seen = set()
return [x for x in seq if x not in seen and not seen.add(x)]
def uniq(seq):
# Not order preserving
return {}.fromkeys(seq).keys()
class PyNMA(object):
"""PyNMA(apikey=[], developerkey=None)
takes 2 optional arguments:
- (opt) apykey: might me a string containing 1 key or an array of keys
- (opt) developerkey: where you can store your developer key
"""
def __init__(self, apikey=[], developerkey=None):
self._developerkey = None
self.developerkey(developerkey)
if apikey:
if type(apikey) == str:
apikey = [apikey]
self._apikey = uniq(apikey)
def addkey(self, key):
"Add a key (register ?)"
if type(key) == str:
if not key in self._apikey:
self._apikey.append(key)
elif type(key) == list:
for k in key:
if not k in self._apikey:
self._apikey.append(k)
def delkey(self, key):
"Removes a key (unregister ?)"
if type(key) == str:
if key in self._apikey:
self._apikey.remove(key)
elif type(key) == list:
for k in key:
if key in self._apikey:
self._apikey.remove(k)
def developerkey(self, developerkey):
"Sets the developer key (and check it has the good length)"
if type(developerkey) == str and len(developerkey) == 48:
self._developerkey = developerkey
def push(self, application="", event="", description="", url="", priority=0, batch_mode=False):
"""Pushes a message on the registered API keys.
takes 5 arguments:
- (req) application: application name [256]
- (req) event: event name [1000]
- (req) description: description [10000]
- (opt) url: url [512]
- (opt) priority: from -2 (lowest) to 2 (highest) (def:0)
- (opt) batch_mode: call API 5 by 5 (def:False)
Warning: using batch_mode will return error only if all API keys are bad
cf: http://nma.usk.bz/api.php
"""
datas = {
'application': application[:256].encode('utf8'),
'event': event[:1024].encode('utf8'),
'description': description[:10000].encode('utf8'),
'priority': priority
}
if url:
datas['url'] = url[:512]
if self._developerkey:
datas['developerkey'] = self._developerkey
results = {}
if not batch_mode:
for key in self._apikey:
datas['apikey'] = key
res = self.callapi('POST', ADD_PATH, datas)
results[key] = res
else:
for i in range(0, len(self._apikey), 5):
datas['apikey'] = ",".join(self._apikey[i:i+5])
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":
headers['Content-type'] = "application/x-www-form-urlencoded"
http_handler = HTTPSConnection(API_SERVER)
http_handler.request(method, path, urlencode(args), headers)
resp = http_handler.getresponse()
try:
res = self._parse_reponse(resp.read())
except Exception, e:
res = {'type': "pynmaerror",
'code': 600,
'message': str(e)
}
pass
return res
def _parse_reponse(self, response):
root = parseString(response).firstChild
for elem in root.childNodes:
if elem.nodeType == elem.TEXT_NODE: continue
if elem.tagName == 'success':
res = dict(elem.attributes.items())
res['message'] = ""
res['type'] = elem.tagName
return res
if elem.tagName == 'error':
res = dict(elem.attributes.items())
res['message'] = elem.firstChild.nodeValue
res['type'] = elem.tagName
return res