FIX:(#1046) if nzbname had '+' within title, would fail to create nzb within cache (nzb only), IMP:(#1079) test notification buttons added, FIX: Changed names of functions to ResumeSeries/DeleteSeries/PauseSeries, IMP: pushbullet notification converted now using requests module, FIX:(#1088) better versioning detection when searching, enforce permissions on cache directory before writing (to ensure nzbs will be written with proper permissions), IMP: Popup dialog box on Delete Series in Comic Details page - option to delete series folder & contents avaiable as well, IMP: Try to determine local IP on startup to allow for better execution of sending nzbs to nzb client

This commit is contained in:
evilhero 2015-07-21 02:37:41 -04:00
parent ef037dce1e
commit abcc4e88ec
13 changed files with 951 additions and 343 deletions

View File

@ -9,7 +9,18 @@
<div id="subhead_container">
<div id="subhead_menu">
<a id="menu_link_refresh" onclick="doAjaxCall('refreshSeries?ComicID=${comic['ComicID']}', $(this),'table')" href="#" data-success="${comic['ComicName']} is being refreshed">Refresh Comic</a>
<a id="menu_link_delete" href="deleteArtist?ComicID=${comic['ComicID']}">Delete Comic</a>
<a id="menu_link_delete" href="#">Delete Comic</a>
<div id="dialog" title="Delete Series Confirmation" style="display:none" class="configtable">
<form action="deleteSeries" method="GET" style="vertical-align: middle; text-align: center">
</br><input type="submit" value="Delete Series">
<div class="row checkbox left clearfix">
</br>
<input type="checkbox" style="vertical-align: middle; margin: 3px; margin-top: -1px;" name="delete_dir" id="deleteCheck" value="1" ${comicConfig['delete_dir']} /><label>Remove directory when deleting Series?</label>
</div>
<input type="hidden" name="ComicID" value=${comic['ComicID']}>
</form>
</div>
%if mylar.RENAME_FILES:
<a id="menu_link_refresh" onclick="doAjaxCall('manualRename?comicid=${comic['ComicID']}', $(this),'table')" data-success="Renaming files.">Rename Files</a>
%endif
@ -18,9 +29,9 @@
<a id="menu_link_refresh" onclick="doAjaxCall('group_metatag?dirName=${comic['ComicLocation'] |u}&ComicID=${comic['ComicID']}', $(this),'table')" data-success="(re)tagging every issue present for '${comic['ComicName']}'">Manual MetaTagging</a>
%endif
%if comic['Status'] == 'Paused':
<a id="menu_link_resume" href="#" onclick="doAjaxCall('resumeArtist?ComicID=${comic['ComicID']}',$(this),true)" data-success="${comic['ComicName']} resumed">Resume Comic</a>
<a id="menu_link_resume" href="#" onclick="doAjaxCall('resumeSeries?ComicID=${comic['ComicID']}',$(this),true)" data-success="${comic['ComicName']} resumed">Resume Comic</a>
%else:
<a id="menu_link_pauze" href="#" onclick="doAjaxCall('pauseArtist?ComicID=${comic['ComicID']}',$(this),true)" data-success="${comic['ComicName']} paused">Pause Comic</a>
<a id="menu_link_pauze" href="#" onclick="doAjaxCall('pauseSeries?ComicID=${comic['ComicID']}',$(this),true)" data-success="${comic['ComicName']} paused">Pause Comic</a>
%endif
%if annuals:
<a id="menu_link_delete" href="annualDelete?comicid=${comic['ComicID']}">Delete Annuals</a>
@ -58,8 +69,9 @@
<li><a href="#tabs-1">Comic Details</a></li>
<li><a href="#tabs-2">Download settings</a></li>
<li><a href="#tabs-3">Edit Settings</a></li>
</ul>
<div id="tabs-1">
</ul>
<div id="tabs-1">
<table class="comictable" summary="Comic Details">
@ -77,39 +89,39 @@
%elif comic['ComicPublisher'] == 'Marvel':
<img src="interfaces/default/images/publisherlogos/logo-marvel.jpg" align="right" alt="Marvel" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Image':
<img src="interfaces/default/images/publisherlogos/logo-imagecomics.png" align="right" alt="Image" height="100" width="50" />
<img src="interfaces/default/images/publisherlogos/logo-imagecomics.png" align="right" alt="Image" height="100" width="50" />
%elif comic['ComicPublisher'] == 'Dark Horse Comics' or comic['ComicPublisher'] == 'Dark Horse':
<img src="interfaces/default/images/publisherlogos/logo-darkhorse.png" align="right" alt="Darkhorse" height="100" width="75" />
<img src="interfaces/default/images/publisherlogos/logo-darkhorse.png" align="right" alt="Darkhorse" height="100" width="75" />
%elif comic['ComicPublisher'] == 'IDW Publishing':
<img src="interfaces/default/images/publisherlogos/logo-idwpublish.png" align="right" alt="IDW" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-idwpublish.png" align="right" alt="IDW" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Icon':
<img src="interfaces/default/images/publisherlogos/logo-iconcomics.png" align="right" alt="Icon" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-iconcomics.png" align="right" alt="Icon" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Red5':
<img src="interfaces/default/images/publisherlogos/logo-red5comics.png" align="right" alt="Red5" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-red5comics.png" align="right" alt="Red5" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Vertigo':
<img src="interfaces/default/images/publisherlogos/logo-vertigo.jpg" align="right" alt="Vertigo" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-vertigo.jpg" align="right" alt="Vertigo" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'ShadowLine':
<img src="interfaces/default/images/publisherlogos/logo-shadowline.png" align="right" alt="Shadowline" height="75" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-shadowline.png" align="right" alt="Shadowline" height="75" width="100"/>
%elif comic['ComicPublisher'] == 'Archie Comics':
<img src="interfaces/default/images/publisherlogos/logo-archiecomics.jpg" align="right" alt="Archie" height="75" width="75"/>
<img src="interfaces/default/images/publisherlogos/logo-archiecomics.jpg" align="right" alt="Archie" height="75" width="75"/>
%elif comic['ComicPublisher'] == 'Oni Press':
<img src="interfaces/default/images/publisherlogos/logo-onipress.png" align="right" alt="Oni Press" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-onipress.png" align="right" alt="Oni Press" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Tokyopop':
<img src="interfaces/default/images/publisherlogos/logo-tokyopop.jpg" align="right" alt="Tokyopop" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-tokyopop.jpg" align="right" alt="Tokyopop" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Midtown Comics':
<img src="interfaces/default/images/publisherlogos/logo-midtowncomics.jpg" align="right" alt="Midtown" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-midtowncomics.jpg" align="right" alt="Midtown" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Boom! Studios':
<img src="interfaces/default/images/publisherlogos/logo-boom.jpg" align="right" alt="Boom!" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-boom.jpg" align="right" alt="Boom!" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Skybound':
<img src="interfaces/default/images/publisherlogos/logo-skybound.jpg" align="right" alt="Skybound" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-skybound.jpg" align="right" alt="Skybound" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Vertigo':
<img src="interfaces/default/images/publisherlogos/logo-dynamite.jpg" align="right" alt="Dynamite" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-dynamite.jpg" align="right" alt="Dynamite" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Top Cow':
<img src="interfaces/default/images/publisherlogos/logo-topcow.gif" align="right" alt="Top Cow" height="75" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-topcow.gif" align="right" alt="Top Cow" height="75" width="100"/>
%elif comic['ComicPublisher'] == 'Dynamite Entertainment':
<img src="interfaces/default/images/publisherlogos/logo-dynamite.png" align="right" alt="Dynamite" height="50" width="100"/>
<img src="interfaces/default/images/publisherlogos/logo-dynamite.png" align="right" alt="Dynamite" height="50" width="100"/>
%elif comic['ComicPublisher'] == 'Cartoon Books':
<img src="interfaces/default/images/publisherlogos/logo-cartoonbooks.jpg" align="right" alt="Cartoon Books" height="75" width="90"/>
<img src="interfaces/default/images/publisherlogos/logo-cartoonbooks.jpg" align="right" alt="Cartoon Books" height="75" width="90"/>
%endif
<fieldset>
<div>
@ -359,7 +371,7 @@
<table class="display" id="issue_table">
<thead>
<tr>
<th id="select" align="left"><input type="checkbox" onClick="toggle(this)" class="checkbox" /></th>
<th id="select" align="left"><input type="checkbox" onClick="toggle(this)" name="issues" class="checkbox" /></th>
<th id="int_issuenumber">IntIssNum</th>
<th id="issuenumber">Number</th>
<th id="issuename">Name</th>
@ -467,6 +479,9 @@
</form>
</div>
<div class="table_wrapper">
%if annuals:
<h1>Annuals</h1>
%for aninfo in annualinfo:
@ -501,7 +516,7 @@
-->
<thead>
<tr>
<th id="select" align="left"><input type="checkbox" onClick="toggle(this)" class="checkbox" /></th>
<th id="ann_action" align="left"><input type="checkbox" onClick="toggle(this)" class="checkbox" /></th>
<th id="aint_issuenumber">Int_IssNumber</th>
<th id="aissuenumber">Number</th>
<th id="aissuename">Name</th>
@ -533,7 +548,7 @@
agrade = 'A'
%>
<tr class="${annual['Status']} grade${agrade}">
<td id="select"><input type="checkbox" name="${annual['IssueID']}" class="checkbox" value="${annual['IssueID']}" /></td>
<td id="ann_action"><input type="checkbox" name="${annual['IssueID']}" class="checkbox" value="${annual['IssueID']}" /></td>
<%
if annual['Int_IssueNumber'] is None:
annual_Number = annual['Int_IssueNumber']
@ -624,7 +639,7 @@
</form>
</div>
%endif
</div>
</%def>
<%def name="headIncludes()">
@ -713,52 +728,12 @@
</script>
<script>
$(function() {
function log( message ) {
$( "<div>" ).text( message ).prependTo( "#log" );
$( "#log" ).scrollTop( 0 );
}
$( "#annseries" ).autocomplete({
source: function( request, response ) {
$.ajax({
url: "http://api.comicvine.com/search?",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: {
resources: "volume",
format: "json",
api_key: "",
query: request.term
},
success: function( data ) {
alert("success");
response( $.map( data.results, function( item ) {
return {
label: item.name + (item.id),
value: item.name
}
}));
},
error: function( data ) {
alert("error");
}
});
},
minLength: 2,
select: function( event, ui ) {
log( ui.item ?
"Selected: " + ui.item.label :
"Nothing selected, input was " + this.value);
},
open: function() {
$( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" );
},
close: function() {
$( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" );
}
});
});
function openDelete() {
$("#dialog").dialog({modal:true});
};
function deleteDirCheck() {
return document.getElementById("deleteCheck").checked;
}
</script>
<script>
@ -796,30 +771,12 @@
});
}
hideServerDivs = function () {
$("#customoptions").slideUp();
$("#hpserveroptions").slideUp();
};
handleNewSelection = function () {
hideServerDivs();
switch ($(this).val()) {
case 'custom':
$("#customoptions").slideDown();
break;
case 'mylar':
$("#hpserveroptions").slideDown();
break;
}
};
function initThisPage(){
$(function() {
$( "#tabs" ).tabs();
});
$("#menu_link_delete").click(openDelete);
initActions();
$('#issue_table').dataTable(
{

View File

@ -899,13 +899,16 @@
<div class="row">
<label>API key</label><input type="text" name="prowl_keys" value="${config['prowl_keys']}" size="50">
</div>
<div class="row checkbox">
<input type="checkbox" name="prowl_onsnatch" value="1" ${config['prowl_onsnatch']} /><label>Notify on snatch?</label>
</div>
<div class="row">
<label>Priority (-2,-1,0,1 or 2):</label>
<input type="text" name="prowl_priority" value="${config['prowl_priority']}" size="2">
</div>
<div class="row checkbox">
<input type="checkbox" name="prowl_onsnatch" value="1" ${config['prowl_onsnatch']} /><label>Notify on snatch?</label>
</div>
<div class="row">
<label>Priority (-2,-1,0,1 or 2):</label>
<input type="text" name="prowl_priority" value="${config['prowl_priority']}" size="2">
</div>
<div class="row">
<input type="button" value="Test PROWL" id="prowl_test" />
</div>
</div>
</fieldset>
<fieldset>
@ -923,8 +926,8 @@
<small>Separate multiple api keys with commas</small>
</div>
<div class="row">
<label>Priority</label>
<select name="nma_priority">
<label>Priority</label>
<select name="nma_priority">
%for x in [-2,-1,0,1,2]:
<%
if config['nma_priority'] == x:
@ -943,19 +946,22 @@
else:
nma_priority_value = 'Emergency'
%>
<option value=${x} ${nma_priority_selected}>${nma_priority_value}</option>
<option value=${x} ${nma_priority_selected}>${nma_priority_value}</option>
%endfor
</select>
</div>
</div>
<div class="row">
<input type="button" value="Test NMA" id="nma_test" />
</div>
</div>
</fieldset>
<fieldset>
<h3><img src="interfaces/default/images/pushover_logo.png" style="vertical-align: middle; margin: 3px; margin-top: -1px;" height="30" width="30"/>Pushover</h3>
<div class="row checkbox">
<input type="checkbox" name="pushover_enabled" id="pushover" value="1" ${config['pushover_enabled']} /><label>Enable Pushover Notifications</label>
</div>
<div id="pushoveroptions">
<h3><img src="interfaces/default/images/pushover_logo.png" style="vertical-align: middle; margin: 3px; margin-top: -1px;" height="30" width="30"/>Pushover</h3>
<div class="row checkbox">
<input type="checkbox" name="pushover_enabled" id="pushover" value="1" ${config['pushover_enabled']} /><label>Enable Pushover Notifications</label>
</div>
<div id="pushoveroptions">
<div class="row">
<label>API key</label><input type="text" title="Leave blank if you don't have your own API (recommended to get your own)" name="pushover_apikey" value="${config['pushover_apikey']}" size="50">
</div>
@ -969,7 +975,10 @@
<label>Priority (-2,-1,0,1 or 2):</label>
<input type="text" name="pushover_priority" value="${config['pushover_priority']}" size="2">
</div>
</div>
<div class="row">
<input type="button" value="Test Pushover" id="pushover_test" />
</div>
</div>
</fieldset>
<fieldset>
<h3><img src="interfaces/default/images/boxcar_logo.png" style="vertical-align: middle; margin: 3px; margin-top: -1px;" height="30" width="30"/>Boxcar.IO</h3>
@ -983,6 +992,9 @@
</div>
<label>Boxcar Token</label>
<input type="text" name="boxcar_token" value="${config['boxcar_token']}" size="30">
<div class="row">
<input type="button" value="Test Boxcar" id="boxcar_test" />
</div>
</div>
</div>
</fieldset>
@ -1007,6 +1019,9 @@
<input type="button" class="btn" value="Update device list" id="getPushbulletDevices" />
-->
</div>
<div class="row">
<input type="button" value="Test Pushbullet" id="pushbullet_test" />
</div>
</div>
</fieldset>
</td>
@ -1286,6 +1301,35 @@
$('#autoadd').append('<input type="hidden" name="tsab" value=1 />');
};
$('#nma_test').click(function () {
$.get("/testNMA",
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
});
$('#prowl_test').click(function () {
$.get("/testprowl",
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
});
$('#pushover_test').click(function () {
$.get("/testpushover",
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
});
$('#boxcar_test').click(function () {
$.get("/testboxcar",
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
});
$('#pushbullet_test').click(function () {
$.get("/testpushbullet",
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
});
$(function() {
$( "#tabs" ).tabs();

93
lib/pystun/README.rst Normal file
View File

@ -0,0 +1,93 @@
.. image:: https://travis-ci.org/jtriley/pystun.svg?branch=master
:target: https://travis-ci.org/jtriley/pystun
.. image:: https://coveralls.io/repos/jtriley/pystun/badge.png
:target: https://coveralls.io/r/jtriley/pystun
PyStun
======
A Python STUN client for getting NAT type and external IP
This is a fork of pystun originally created by gaohawk (http://code.google.com/p/pystun/)
PyStun follows RFC 3489: http://www.ietf.org/rfc/rfc3489.txt
A server following STUN-bis hasn't been found on internet so RFC3489 is the
only implementation.
Installation
------------
To install the latest version::
$ pip install pystun
or download/clone the source and install manually using::
$ cd /path/to/pystun/src
$ python setup.py install
If you're hacking on pystun you should use the 'develop' command instead::
$ python setup.py develop
This will make a link to the sources inside your site-packages directory so
that any changes are immediately available for testing.
Usage
-----
From command line::
$ pystun
NAT Type: Full Cone
External IP: <your-ip-here>
External Port: 54320
Pass --help for more options::
% pystun --help
usage: pystun [-h] [-d] [-H STUN_HOST] [-P STUN_PORT] [-i SOURCE_IP]
[-p SOURCE_PORT] [--version]
optional arguments:
-h, --help show this help message and exit
-d, --debug Enable debug logging (default: False)
-H STUN_HOST, --host STUN_HOST
STUN host to use (default: None)
-P STUN_PORT, --host-port STUN_PORT
STUN host port to use (default: 3478)
-i SOURCE_IP, --interface SOURCE_IP
network interface for client (default: 0.0.0.0)
-p SOURCE_PORT, --port SOURCE_PORT
port to listen on for client (default: 54320)
--version show program's version number and exit
From Python::
import stun
nat_type, external_ip, external_port = stun.get_ip_info()
This will rotate through an internal list of STUN servers until a response is
found. If no response is found you will get "Blocked" as the *nat_type* and
**None** for *external_ip* and *external_port*.
If you prefer to use a specific STUN server::
nat_type, external_ip, external_port = stun.get_ip_info(stun_host='stun.ekiga.net')
If you prefer to use a specific STUN server port::
nat_type, external_ip, external_port = stun.get_ip_info(stun_port=3478)
You may also specify the client interface and port that is used although this
is not needed::
sip = "0.0.0.0" # interface to listen on (all)
port = 54320 # port to listen on
nat_type, external_ip, external_port = stun.get_ip_info(sip, port)
Read the code for more details...
LICENSE
-------
MIT

257
lib/pystun/__init__.py Normal file
View File

@ -0,0 +1,257 @@
import binascii
import logging
import random
import socket
__version__ = '0.1.0'
log = logging.getLogger("pystun")
STUN_SERVERS = (
'stun.ekiga.net',
'stun.ideasip.com',
'stun.voiparound.com',
'stun.voipbuster.com',
'stun.voipstunt.com',
'stun.voxgratia.org'
)
stun_servers_list = STUN_SERVERS
DEFAULTS = {
'stun_port': 3478,
'source_ip': '0.0.0.0',
'source_port': 54320
}
# stun attributes
MappedAddress = '0001'
ResponseAddress = '0002'
ChangeRequest = '0003'
SourceAddress = '0004'
ChangedAddress = '0005'
Username = '0006'
Password = '0007'
MessageIntegrity = '0008'
ErrorCode = '0009'
UnknownAttribute = '000A'
ReflectedFrom = '000B'
XorOnly = '0021'
XorMappedAddress = '8020'
ServerName = '8022'
SecondaryAddress = '8050' # Non standard extension
# types for a stun message
BindRequestMsg = '0001'
BindResponseMsg = '0101'
BindErrorResponseMsg = '0111'
SharedSecretRequestMsg = '0002'
SharedSecretResponseMsg = '0102'
SharedSecretErrorResponseMsg = '0112'
dictAttrToVal = {'MappedAddress': MappedAddress,
'ResponseAddress': ResponseAddress,
'ChangeRequest': ChangeRequest,
'SourceAddress': SourceAddress,
'ChangedAddress': ChangedAddress,
'Username': Username,
'Password': Password,
'MessageIntegrity': MessageIntegrity,
'ErrorCode': ErrorCode,
'UnknownAttribute': UnknownAttribute,
'ReflectedFrom': ReflectedFrom,
'XorOnly': XorOnly,
'XorMappedAddress': XorMappedAddress,
'ServerName': ServerName,
'SecondaryAddress': SecondaryAddress}
dictMsgTypeToVal = {
'BindRequestMsg': BindRequestMsg,
'BindResponseMsg': BindResponseMsg,
'BindErrorResponseMsg': BindErrorResponseMsg,
'SharedSecretRequestMsg': SharedSecretRequestMsg,
'SharedSecretResponseMsg': SharedSecretResponseMsg,
'SharedSecretErrorResponseMsg': SharedSecretErrorResponseMsg}
dictValToMsgType = {}
dictValToAttr = {}
Blocked = "Blocked"
OpenInternet = "Open Internet"
FullCone = "Full Cone"
SymmetricUDPFirewall = "Symmetric UDP Firewall"
RestricNAT = "Restric NAT"
RestricPortNAT = "Restric Port NAT"
SymmetricNAT = "Symmetric NAT"
ChangedAddressError = "Meet an error, when do Test1 on Changed IP and Port"
def _initialize():
items = dictAttrToVal.items()
for i in range(len(items)):
dictValToAttr.update({items[i][1]: items[i][0]})
items = dictMsgTypeToVal.items()
for i in range(len(items)):
dictValToMsgType.update({items[i][1]: items[i][0]})
def gen_tran_id():
a = ''.join(random.choice('0123456789ABCDEF') for i in range(32))
# return binascii.a2b_hex(a)
return a
def stun_test(sock, host, port, source_ip, source_port, send_data=""):
retVal = {'Resp': False, 'ExternalIP': None, 'ExternalPort': None,
'SourceIP': None, 'SourcePort': None, 'ChangedIP': None,
'ChangedPort': None}
str_len = "%#04d" % (len(send_data) / 2)
tranid = gen_tran_id()
str_data = ''.join([BindRequestMsg, str_len, tranid, send_data])
data = binascii.a2b_hex(str_data)
recvCorr = False
while not recvCorr:
recieved = False
count = 3
while not recieved:
log.debug("sendto: %s", (host, port))
try:
sock.sendto(data, (host, port))
except socket.gaierror:
retVal['Resp'] = False
return retVal
try:
buf, addr = sock.recvfrom(2048)
log.debug("recvfrom: %s", addr)
recieved = True
except Exception:
recieved = False
if count > 0:
count -= 1
else:
retVal['Resp'] = False
return retVal
msgtype = binascii.b2a_hex(buf[0:2])
bind_resp_msg = dictValToMsgType[msgtype] == "BindResponseMsg"
tranid_match = tranid.upper() == binascii.b2a_hex(buf[4:20]).upper()
if bind_resp_msg and tranid_match:
recvCorr = True
retVal['Resp'] = True
len_message = int(binascii.b2a_hex(buf[2:4]), 16)
len_remain = len_message
base = 20
while len_remain:
attr_type = binascii.b2a_hex(buf[base:(base + 2)])
attr_len = int(binascii.b2a_hex(buf[(base + 2):(base + 4)]), 16)
if attr_type == MappedAddress:
port = int(binascii.b2a_hex(buf[base + 6:base + 8]), 16)
ip = ".".join([
str(int(binascii.b2a_hex(buf[base + 8:base + 9]), 16)),
str(int(binascii.b2a_hex(buf[base + 9:base + 10]), 16)),
str(int(binascii.b2a_hex(buf[base + 10:base + 11]), 16)),
str(int(binascii.b2a_hex(buf[base + 11:base + 12]), 16))
])
retVal['ExternalIP'] = ip
retVal['ExternalPort'] = port
if attr_type == SourceAddress:
port = int(binascii.b2a_hex(buf[base + 6:base + 8]), 16)
ip = ".".join([
str(int(binascii.b2a_hex(buf[base + 8:base + 9]), 16)),
str(int(binascii.b2a_hex(buf[base + 9:base + 10]), 16)),
str(int(binascii.b2a_hex(buf[base + 10:base + 11]), 16)),
str(int(binascii.b2a_hex(buf[base + 11:base + 12]), 16))
])
retVal['SourceIP'] = ip
retVal['SourcePort'] = port
if attr_type == ChangedAddress:
port = int(binascii.b2a_hex(buf[base + 6:base + 8]), 16)
ip = ".".join([
str(int(binascii.b2a_hex(buf[base + 8:base + 9]), 16)),
str(int(binascii.b2a_hex(buf[base + 9:base + 10]), 16)),
str(int(binascii.b2a_hex(buf[base + 10:base + 11]), 16)),
str(int(binascii.b2a_hex(buf[base + 11:base + 12]), 16))
])
retVal['ChangedIP'] = ip
retVal['ChangedPort'] = port
# if attr_type == ServerName:
# serverName = buf[(base+4):(base+4+attr_len)]
base = base + 4 + attr_len
len_remain = len_remain - (4 + attr_len)
# s.close()
return retVal
def get_nat_type(s, source_ip, source_port, stun_host=None, stun_port=3478):
_initialize()
port = stun_port
log.debug("Do Test1")
resp = False
if stun_host:
ret = stun_test(s, stun_host, port, source_ip, source_port)
resp = ret['Resp']
else:
for stun_host in stun_servers_list:
log.debug('Trying STUN host: %s', stun_host)
ret = stun_test(s, stun_host, port, source_ip, source_port)
resp = ret['Resp']
if resp:
break
if not resp:
return Blocked, ret
log.debug("Result: %s", ret)
exIP = ret['ExternalIP']
exPort = ret['ExternalPort']
changedIP = ret['ChangedIP']
changedPort = ret['ChangedPort']
if ret['ExternalIP'] == source_ip:
changeRequest = ''.join([ChangeRequest, '0004', "00000006"])
ret = stun_test(s, stun_host, port, source_ip, source_port,
changeRequest)
if ret['Resp']:
typ = OpenInternet
else:
typ = SymmetricUDPFirewall
else:
changeRequest = ''.join([ChangeRequest, '0004', "00000006"])
log.debug("Do Test2")
ret = stun_test(s, stun_host, port, source_ip, source_port,
changeRequest)
log.debug("Result: %s", ret)
if ret['Resp']:
typ = FullCone
else:
log.debug("Do Test1")
ret = stun_test(s, changedIP, changedPort, source_ip, source_port)
log.debug("Result: %s", ret)
if not ret['Resp']:
typ = ChangedAddressError
else:
if exIP == ret['ExternalIP'] and exPort == ret['ExternalPort']:
changePortRequest = ''.join([ChangeRequest, '0004',
"00000002"])
log.debug("Do Test3")
ret = stun_test(s, changedIP, port, source_ip, source_port,
changePortRequest)
log.debug("Result: %s", ret)
if ret['Resp']:
typ = RestricNAT
else:
typ = RestricPortNAT
else:
typ = SymmetricNAT
return typ, ret
def get_ip_info(source_ip="0.0.0.0", source_port=54320, stun_host=None,
stun_port=3478):
socket.setdefaulttimeout(2)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((source_ip, source_port))
nat_type, nat = get_nat_type(s, source_ip, source_port,
stun_host=stun_host, stun_port=stun_port)
external_ip = nat['ExternalIP']
external_port = nat['ExternalPort']
s.close()
return (nat_type, external_ip, external_port)

64
lib/pystun/cli.py Normal file
View File

@ -0,0 +1,64 @@
from __future__ import print_function
import argparse
import logging
import sys
import stun
def make_argument_parser():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
'-d', '--debug', action='store_true',
help='Enable debug logging'
)
parser.add_argument(
'-H', '--stun-host',
help='STUN host to use'
)
parser.add_argument(
'-P', '--stun-port', type=int,
default=stun.DEFAULTS['stun_port'],
help='STUN host port to use'
)
parser.add_argument(
'-i', '--source-ip',
default=stun.DEFAULTS['source_ip'],
help='network interface for client'
)
parser.add_argument(
'-p', '--source-port', type=int,
default=stun.DEFAULTS['source_port'],
help='port to listen on for client'
)
parser.add_argument('--version', action='version', version=stun.__version__)
return parser
def main():
try:
options = make_argument_parser().parse_args()
if options.debug:
logging.basicConfig()
stun.log.setLevel(logging.DEBUG)
nat_type, external_ip, external_port = stun.get_ip_info(
source_ip=options.source_ip,
source_port=options.source_port,
stun_host=options.stun_host,
stun_port=options.stun_port
)
print('NAT Type:', nat_type)
print('External IP:', external_ip)
print('External Port:', external_port)
except KeyboardInterrupt:
sys.exit()
if __name__ == '__main__':
main()

View File

View File

@ -0,0 +1,61 @@
import argparse
import unittest
import stun
from stun import cli
class TestCLI(unittest.TestCase):
"""Test the CLI API."""
@classmethod
def setUpClass(cls):
cls.source_ip = '123.45.67.89'
cls.source_port = 24816
cls.stun_port = 13579
cls.stun_host = 'stun.stub.org'
def test_cli_parser_default(self):
parser = cli.make_argument_parser()
options = parser.parse_args([])
self.assertEqual(options.source_ip, stun.DEFAULTS['source_ip'])
self.assertEqual(options.source_port, stun.DEFAULTS['source_port'])
self.assertEqual(options.stun_port, stun.DEFAULTS['stun_port'])
self.assertIsNone(options.stun_host)
def test_cli_parser_user_long_form(self):
parser = cli.make_argument_parser()
options = parser.parse_args([
'--source-port', str(self.source_port),
'--source-ip', self.source_ip,
'--stun-port', str(self.stun_port),
'--stun-host', self.stun_host,
'--debug'
])
self.assertTrue(options.debug)
self.assertEqual(options.source_ip, self.source_ip)
self.assertEqual(options.source_port, self.source_port)
self.assertEqual(options.stun_host, self.stun_host)
self.assertEqual(options.stun_port, self.stun_port)
def test_cli_parser_user_short_form(self):
parser = cli.make_argument_parser()
options = parser.parse_args([
'-p', str(self.source_port),
'-i', self.source_ip,
'-P', str(self.stun_port),
'-H', self.stun_host,
'-d'
])
self.assertTrue(options.debug)
self.assertEqual(options.source_ip, self.source_ip)
self.assertEqual(options.source_port, self.source_port)
self.assertEqual(options.stun_host, self.stun_host)
self.assertEqual(options.stun_port, self.stun_port)
if __name__ == '__main__':
unittest.main()

View File

@ -174,10 +174,10 @@ class PostProcessor(object):
def Process(self):
module = self.module
self._log("nzb name: " + str(self.nzb_name))
self._log("nzb folder: " + str(self.nzb_folder))
logger.fdebug(module + ' nzb name: ' + str(self.nzb_name))
logger.fdebug(module + ' nzb folder: ' + str(self.nzb_folder))
self._log("nzb name: " + self.nzb_name)
self._log("nzb folder: " + self.nzb_folder)
logger.fdebug(module + ' nzb name: ' + self.nzb_name)
logger.fdebug(module + ' nzb folder: ' + self.nzb_folder)
if mylar.USE_SABNZBD==0:
logger.fdebug(module + ' Not using SABnzbd')
elif mylar.USE_SABNZBD != 0 and self.nzb_name == 'Manual Run':
@ -606,7 +606,7 @@ class PostProcessor(object):
logger.warn(module + ' Failed to remove temporary directory - check directory and manually re-run.')
return
logger.fdebug(module + ' Removed temporary directory : ' + str(self.nzb_folder))
logger.fdebug(module + ' Removed temporary directory : ' + self.nzb_folder)
#delete entry from nzblog table
IssArcID = 'S' + str(ml['IssueArcID'])
@ -635,7 +635,7 @@ class PostProcessor(object):
logger.fdebug('[NZBNAME]: ' + nzbname)
#gotta replace & or escape it
nzbname = re.sub("\&", 'and', nzbname)
nzbname = re.sub('[\,\:\?\']', '', nzbname)
nzbname = re.sub('[\,\:\?\'\+]', '', nzbname)
nzbname = re.sub('[\(\)]', ' ', nzbname)
logger.fdebug('[NZBNAME] nzbname (remove chars): ' + nzbname)
nzbname = re.sub('.cbr', '', nzbname).strip()
@ -830,7 +830,7 @@ class PostProcessor(object):
logger.debug(module + ' Failed to remove temporary directory - check directory and manually re-run.')
return
logger.debug(module + ' Removed temporary directory : ' + str(self.nzb_folder))
logger.debug(module + ' Removed temporary directory : ' + self.nzb_folder)
self._log("Removed temporary directory : " + self.nzb_folder)
#delete entry from nzblog table
myDB.action('DELETE from nzblog WHERE issueid=?', [issueid])
@ -1223,7 +1223,7 @@ class PostProcessor(object):
logger.fdebug(module + ' ext:' + ext)
if ofilename is None:
logger.error(module + ' Aborting PostProcessing - the filename does not exist in the location given. Make sure that ' + str(self.nzb_folder) + ' exists and is the correct location.')
logger.error(module + ' Aborting PostProcessing - the filename does not exist in the location given. Make sure that ' + self.nzb_folder + ' exists and is the correct location.')
self.valreturn.append({"self.log": self.log,
"mode": 'stop'})
return self.queue.put(self.valreturn)
@ -1302,7 +1302,7 @@ class PostProcessor(object):
self.valreturn.append({"self.log": self.log,
"mode": 'stop'})
return self.queue.put(self.valreturn)
self._log("Removed temporary directory : " + str(self.nzb_folder))
self._log("Removed temporary directory : " + self.nzb_folder)
logger.fdebug(module + ' Removed temporary directory : ' + self.nzb_folder)
else:
#downtype = for use with updater on history table to set status to 'Post-Processed'

View File

@ -104,6 +104,8 @@ DONATEBUTTON = True
PULLNEW = None
ALT_PULL = False
LOCAL_IP = None
EXT_IP = None
HTTP_PORT = None
HTTP_HOST = None
HTTP_USERNAME = None
@ -147,6 +149,7 @@ CHOWNER = None
CHGROUP = None
USENET_RETENTION = None
CREATE_FOLDERS = True
DELETE_REMOVE_DIR = False
ADD_COMICS = False
COMIC_DIR = None
@ -402,8 +405,8 @@ def initialize():
with INIT_LOCK:
global __INITIALIZED__, DBCHOICE, DBUSER, DBPASS, DBNAME, COMICVINE_API, DEFAULT_CVAPI, CVAPI_COUNT, CVAPI_TIME, CVAPI_MAX, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, UPCOMING_SNATCHED, COMICSORT, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, MAX_LOGSIZE, LOGVERBOSE, OLDCONFIG_VERSION, OS_DETECT, \
queue, HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, HTTPS_FORCE_ON, HOST_RETURN, API_ENABLED, API_KEY, DOWNLOAD_APIKEY, LAUNCH_BROWSER, GIT_PATH, SAFESTART, AUTO_UPDATE, \
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, GIT_USER, GIT_BRANCH, USER_AGENT, DESTINATION_DIR, MULTIPLE_DEST_DIRS, CREATE_FOLDERS, \
queue, LOCAL_IP, EXT_IP, HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, HTTPS_FORCE_ON, HOST_RETURN, API_ENABLED, API_KEY, DOWNLOAD_APIKEY, LAUNCH_BROWSER, GIT_PATH, SAFESTART, AUTO_UPDATE, \
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, GIT_USER, GIT_BRANCH, USER_AGENT, DESTINATION_DIR, MULTIPLE_DEST_DIRS, CREATE_FOLDERS, DELETE_REMOVE_DIR, \
DOWNLOAD_DIR, USENET_RETENTION, SEARCH_INTERVAL, NZB_STARTUP_SEARCH, INTERFACE, DUPECONSTRAINT, AUTOWANT_ALL, AUTOWANT_UPCOMING, ZERO_LEVEL, ZERO_LEVEL_N, COMIC_COVER_LOCAL, HIGHCOUNT, \
LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, NZB_DOWNLOADER, USE_SABNZBD, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_PRIORITY, SAB_TO_MYLAR, SAB_DIRECTORY, USE_BLACKHOLE, BLACKHOLE_DIR, ADD_COMICS, COMIC_DIR, IMP_MOVE, IMP_RENAME, IMP_METADATA, \
USE_NZBGET, NZBGET_HOST, NZBGET_PORT, NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_PRIORITY, NZBGET_DIRECTORY, NZBSU, NZBSU_UID, NZBSU_APIKEY, DOGNZB, DOGNZB_APIKEY, \
@ -491,6 +494,7 @@ def initialize():
DESTINATION_DIR = check_setting_str(CFG, 'General', 'destination_dir', '')
MULTIPLE_DEST_DIRS = check_setting_str(CFG, 'General', 'multiple_dest_dirs', '')
CREATE_FOLDERS = bool(check_setting_int(CFG, 'General', 'create_folders', 1))
DELETE_REMOVE_DIR = bool(check_setting_int(CFG, 'General', 'delete_remove_dir', 0))
CHMOD_DIR = check_setting_str(CFG, 'General', 'chmod_dir', '0777')
CHMOD_FILE = check_setting_str(CFG, 'General', 'chmod_file', '0660')
CHOWNER = check_setting_str(CFG, 'General', 'chowner', '')
@ -923,6 +927,19 @@ def initialize():
# Start the logger, silence console logging if we need to
logger.initLogger(verbose=VERBOSE) #logger.mylar_log.initLogger(verbose=VERBOSE)
#try to get the local IP using socket. Get this on every startup so it's at least current for existing session.
import socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
LOCAL_IP = s.getsockname()[0]
s.close()
logger.info('Successfully discovered local IP and locking it in as : ' + str(LOCAL_IP))
except:
logger.warn('Unable to determine local IP - this might cause problems when downloading (maybe use host_return in the config.ini)')
LOCAL_IP = HTTP_HOST
# verbatim back the logger being used since it's now started.
if LOGTYPE == 'clog':
logprog = 'Concurrent Rotational Log Handler'
@ -1024,11 +1041,6 @@ def initialize():
logger.info('Remapping the sorting to allow for new additions.')
COMICSORT = helpers.ComicSort(sequence='startup')
#start the db write only thread here.
#this is a thread that continually runs in the background as the ONLY thread that can write to the db.
#logger.info('Starting Write-Only thread.')
#db.WriteOnly()
#initialize the scheduler threads here.
dbUpdateScheduler = scheduler.Scheduler(action=dbupdater.dbUpdate(),
cycleTime=datetime.timedelta(hours=48),
@ -1191,6 +1203,7 @@ def config_write():
new_config['General']['destination_dir'] = DESTINATION_DIR
new_config['General']['multiple_dest_dirs'] = MULTIPLE_DEST_DIRS
new_config['General']['create_folders'] = int(CREATE_FOLDERS)
new_config['General']['delete_remove_dir'] = int(DELETE_REMOVE_DIR)
new_config['General']['chmod_dir'] = CHMOD_DIR
new_config['General']['chmod_file'] = CHMOD_FILE
new_config['General']['chowner'] = CHOWNER

View File

@ -1239,7 +1239,7 @@ def setperms(path, dir=False):
os.chown(os.path.join(root, momo), chowner, chgroup)
os.chmod(os.path.join(root, momo), permission)
logger.info('Successfully changed ownership and permissions [' + str(mylar.CHOWNER) + ':' + str(mylar.CHGROUP) + '] / [' + str(mylar.CHMOD_DIR) + ' / ' + str(mylar.CHMOD_FILE) + ']')
logger.fdebug('Successfully changed ownership and permissions [' + str(mylar.CHOWNER) + ':' + str(mylar.CHGROUP) + '] / [' + str(mylar.CHMOD_DIR) + ' / ' + str(mylar.CHMOD_FILE) + ']')
else:
for root, dirs, files in os.walk(path):
@ -1250,7 +1250,7 @@ def setperms(path, dir=False):
permission = int(mylar.CHMOD_FILE, 8)
os.chmod(os.path.join(root, momo), permission)
logger.info('Successfully changed permissions [' + str(mylar.CHMOD_DIR) + ' / ' + str(mylar.CHMOD_FILE) + ']')
logger.fdebug('Successfully changed permissions [' + str(mylar.CHMOD_DIR) + ' / ' + str(mylar.CHMOD_FILE) + ']')
except OSError:
logger.error('Could not change permissions : ' + path + '. Exiting...')

View File

@ -26,6 +26,7 @@ import subprocess
import time
import lib.simplejson as simplejson
import json
import lib.requests as requests
# This was obviously all taken from headphones with great appreciation :)
@ -80,12 +81,7 @@ class PROWL:
#For uniformity reasons not removed
return
def test(self, keys, priority):
self.enabled = True
self.keys = keys
self.priority = priority
def test_notify(self):
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
class NMA:
@ -137,6 +133,9 @@ class NMA:
if not request:
logger.warn(module + ' Error sending notification request to NotifyMyAndroid')
def test_notify(self):
self.notify(prline='Test Message',prline2='ZOMG Lazors Pewpewpew!')
# 2013-04-01 Added Pushover.net notifications, based on copy of Prowl class above.
# No extra care has been put into API friendliness at the moment (read: https://pushover.net/api#friendly)
class PUSHOVER:
@ -198,13 +197,7 @@ class PUSHOVER:
logger.info(module + ' Pushover notification failed.')
return False
def test(self, apikey, userkey, priority):
self.enabled = True
self.apikey = apikey
self.userkey = userkey
self.priority = priority
def test_notify(self):
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
@ -284,11 +277,20 @@ class BOXCAR:
self._sendBoxcar(message, title, module)
return True
def test_notify(self):
self.notify(prline='Test Message',prline2='ZOMG Lazors Pewpewpew!')
class PUSHBULLET:
def __init__(self):
self.PUSH_URL = "https://api.pushbullet.com/v2/pushes"
self.apikey = mylar.PUSHBULLET_APIKEY
self.deviceid = mylar.PUSHBULLET_DEVICEID
self._json_header = {'Content-Type': 'application/json'}
self._session = requests.Session()
self._session.auth = (self.apikey, "")
self._session.headers.update(self._json_header)
def get_devices(self, api):
return self.notify(method="GET")
@ -299,19 +301,20 @@ class PUSHBULLET:
if module is None:
module = ''
module += '[NOTIFIER]'
# http_handler = HTTPSConnection("api.pushbullet.com")
http_handler = HTTPSConnection("api.pushbullet.com")
# if method == 'GET':
# uri = '/v2/devices'
# else:
# method = 'POST'
# uri = '/v2/pushes'
# authString = base64.b64encode(self.apikey + ":")
if method == 'GET':
uri = '/v2/devices'
else:
method = 'POST'
uri = '/v2/pushes'
authString = base64.b64encode(self.apikey + ":")
if method == 'GET':
http_handler.request(method, uri, None, headers={'Authorization': 'Basic %s:' % authString})
pass
# http_handler.request(method, uri, None, headers={'Authorization': 'Basic %s:' % authString})
else:
if snatched:
if snatched[-1] == '.': snatched = snatched[:-1]
@ -325,37 +328,35 @@ class PUSHBULLET:
'title': event.encode('utf-8'), #"mylar",
'body': message.encode('utf-8')}
http_handler.request("POST",
"/v2/pushes",
headers = {'Content-type': "application/json",
'Authorization': 'Basic %s' % base64.b64encode(mylar.PUSHBULLET_APIKEY + ":")},
body = json.dumps(data))
r = self._session.post(self.PUSH_URL, data=json.dumps(data))
response = http_handler.getresponse()
request_body = response.read()
request_status = response.status
#logger.fdebug(u"PushBullet response status: %r" % request_status)
#logger.fdebug(u"PushBullet response headers: %r" % response.getheaders())
#logger.fdebug(u"PushBullet response body: %r" % response.read())
if request_status == 200:
# http_handler.request("POST",
# "/v2/pushes",
# headers = {'Content-type': "application/json",
# 'Authorization': 'Basic %s' % base64.b64encode(mylar.PUSHBULLET_APIKEY + ":")},
# body = json.dumps(data))
#
# response = http_handler.getresponse()
# request_body = response.read()
# request_status = response.status
# #logger.fdebug(u"PushBullet response status: %r" % request_status)
# #logger.fdebug(u"PushBullet response headers: %r" % response.getheaders())
# #logger.fdebug(u"PushBullet response body: %r" % response.read())
if r.status_code == 200:
if method == 'GET':
return request_body
return r.json()
else:
logger.info(module + ' PushBullet notifications sent.')
return True
elif request_status >= 400 and request_status < 500:
logger.error(module + ' PushBullet request failed: %s' % response.reason)
elif r.status_code >= 400 and r.status_code < 500:
logger.error(module + ' PushBullet request failed: %s' % r.content)
return False
else:
logger.error(module + ' PushBullet notification failed serverside.')
return False
def test(self, apikey, deviceid):
self.enabled = True
self.apikey = apikey
self.deviceid = deviceid
self.notify('Main Screen Activate', 'Test Message')
def test_notify(self):
self.notify(prline='Test Message', prline2='Release the Ninjas!')

View File

@ -16,7 +16,7 @@
from __future__ import division
import mylar
from mylar import logger, db, updater, helpers, parseit, findcomicfeed, notifiers, rsscheck, Failed
from mylar import logger, db, updater, helpers, parseit, findcomicfeed, notifiers, rsscheck, Failed, filechecker
import lib.feedparser as feedparser
import urllib
@ -474,6 +474,10 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa
if nzbprov == '':
bb = "no results"
elif nzbprov == '32P':
#cmname = re.sub("%20", " ", str(comsrc))
#bb = rsscheck.torrents(pickfeed='4', seriesname=cmname, issue=mod_isssearch)
#rss = "no"
#logger.info('bb returned: ' + str(bb))
bb = "no results"
elif nzbprov == 'KAT':
cmname = re.sub("%20", " ", str(comsrc))
@ -752,44 +756,64 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa
ComVersChk = 0
ctchk = cleantitle.split()
volfound = False
vol_label = None
fndcomicversion = None
for ct in ctchk:
if ct.lower().startswith('v') and ct[1:].isdigit():
logger.fdebug("possible versioning..checking")
#we hit a versioning # - account for it
if ct[1:].isdigit():
if len(ct[1:]) == 4: #v2013
logger.fdebug("Version detected as " + str(ct))
vers4year = "yes" #re.sub("[^0-9]", " ", str(ct)) #remove the v
#cleantitle = re.sub(ct, "(" + str(vers4year) + ")", cleantitle)
#logger.fdebug("volumized cleantitle : " + cleantitle)
versionfound = "yes"
break
if any([ct.lower().startswith('v') and ct[1:].isdigit(), ct.lower()[:3] == 'vol', volfound == True]):
if volfound == True:
logger.fdebug('Split Volume label detected - ie. Vol 4. Attempting to adust.')
if ct.isdigit():
vol_label = vol_label + ' ' + str(ct)
ct = 'v' + str(ct)
volfound == False
cleantitle = re.sub(vol_label, ct, cleantitle).strip()
tmpsplit = ct
if tmpsplit.lower().startswith('vol'):
logger.fdebug('volume detected - stripping and re-analzying for volume label.')
if '.' in tmpsplit:
tmpsplit = re.sub('.', '', tmpsplit).strip()
tmpsplit = re.sub('vol', '', tmpsplit.lower()).strip()
#if vol label set as 'Vol 4' it will obliterate the Vol, but pass over the '4' - set
#volfound to True so that it can loop back around.
if not tmpsplit.isdigit():
vol_label = ct #store the wording of how the Vol is defined so we can skip it later on.
volfound = True
continue
if len(tmpsplit[1:]) == 4 and tmpsplit[1:].isdigit(): #v2013
logger.fdebug("[Vxxxx] Version detected as " + str(tmpsplit))
vers4year = "yes" #re.sub("[^0-9]", " ", str(ct)) #remove the v
fndcomicversion = str(tmpsplit)
elif len(tmpsplit[1:]) == 1 and tmpsplit[1:].isdigit(): #v2
logger.fdebug("[Vx] Version detected as " + str(tmpsplit))
vers4vol = str(tmpsplit)
fndcomicversion = str(tmpsplit)
elif tmpsplit[1:].isdigit() and len(tmpsplit) < 4:
logger.fdebug('[Vxxx] Version detected as ' +str(tmpsplit))
vers4vol = str(tmpsplit)
fndcomicversion = str(tmpsplit)
elif tmpsplit.isdigit() and len(tmpsplit) <=4:
# this stuff is necessary for 32P volume manipulation
if len(tmpsplit) == 4:
vers4year = "yes"
fndcomicversion = str(tmpsplit)
elif len(tmpsplit) == 1:
vers4vol = str(tmpsplit)
fndcomicversion = str(tmpsplit)
elif len(tmpsplit) < 4:
vers4vol = str(tmpsplit)
fndcomicversion = str(tmpsplit)
else:
if len(ct) < 4:
logger.fdebug("Version detected as " + str(ct))
vers4vol = str(ct)
versionfound = "yes"
break
logger.fdebug("false version detection..ignoring.")
elif ct.lower()[:3] == 'vol':
#if in format vol.2013/vol2013/vol01/vol.1, etc
ct = re.sub('vol', '', ct.lower())
if '.' in ct: re.sub('.', '', ct).strip()
if ct.lower()[4:].isdigit():
logger.fdebug('volume indicator detected as version #:' + str(ct))
vers4year = "yes"
versionfound = "yes"
break
logger.fdebug("error - unknown length for : " + str(tmpsplit))
continue
else:
vers4vol = ct
versionfound = "yes"
logger.fdebug('volume indicator detected as version #:' + str(vers4vol))
break
logger.fdebug("false version detection..ignoring.")
logger.fdebug("error - unknown length for : " + str(tmpsplit))
continue
if fndcomicversion:
versionfound = "yes"
break
if len(re.findall('[^()]+', cleantitle)) == 1 or 'cover only' in cleantitle.lower():
#some sites don't have (2013) or whatever..just v2 / v2013. Let's adjust:
@ -1064,112 +1088,113 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa
#splitst = splitst - 1
if versionfound == "yes":
volfound = False
for tstsplit in splitit:
logger.fdebug('comparing ' + str(tstsplit))
if volfound == True:
logger.fdebug('Split Volume label detected - ie. Vol 4. Attempting to adust.')
if tstsplit.isdigit():
tstsplit = 'v' + str(tstsplit)
volfound == False
if tstsplit.lower().startswith('v'): #tstsplit[1:].isdigit():
logger.fdebug("this has a version #...let's adjust")
tmpsplit = tstsplit
if tmpsplit.lower().startswith('vol'):
logger.fdebug('volume detected - stripping and re-analzying for volume label.')
if '.' in tmpsplit:
tmpsplit = re.sub('.', '', tmpsplit).strip()
tmpsplit = re.sub('vol', '', tmpsplit.lower()).strip()
#if vol label set as 'Vol 4' it will obliterate the Vol, but pass over the '4' - set
#volfound to True so that it can loop back around.
if not tmpsplit.isdigit():
volfound = True
continue
if len(tmpsplit[1:]) == 4 and tmpsplit[1:].isdigit(): #v2013
logger.fdebug("[Vxxxx] Version detected as " + str(tmpsplit))
vers4year = "yes" #re.sub("[^0-9]", " ", str(ct)) #remove the v
elif len(tmpsplit[1:]) == 1 and tmpsplit[1:].isdigit(): #v2
logger.fdebug("[Vx] Version detected as " + str(tmpsplit))
vers4vol = str(tmpsplit)
elif tmpsplit[1:].isdigit() and len(tmpsplit) < 4:
logger.fdebug('[Vxxx] Version detected as ' +str(tmpsplit))
vers4vol = str(tmpsplit)
elif tmpsplit.isdigit() and len(tmpsplit) <=4:
# this stuff is necessary for 32P volume manipulation
if len(tmpsplit) == 4:
vers4year = "yes"
elif len(tmpsplit) == 1:
vers4vol = str(tmpsplit)
elif len(tmpsplit) < 4:
vers4vol = str(tmpsplit)
else:
logger.fdebug("error - unknown length for : " + str(tmpsplit))
continue
else:
logger.fdebug("error - unknown length for : " + str(tmpsplit))
continue
# volfound = False
# vol_label = None
# for tstsplit in splitit:
# logger.fdebug('comparing ' + str(tstsplit))
# if volfound == True:
# logger.fdebug('Split Volume label detected - ie. Vol 4. Attempting to adust.')
# if tstsplit.isdigit():
# vol_label = vol_label + ' ' + str(tstsplit)
# tstsplit = 'v' + str(tstsplit)
# volfound == False
# if tstsplit.lower().startswith('v'): #tstsplit[1:].isdigit():
# logger.fdebug("this has a version #...let's adjust")
# tmpsplit = tstsplit
# if tmpsplit.lower().startswith('vol'):
# logger.fdebug('volume detected - stripping and re-analzying for volume label.')
# if '.' in tmpsplit:
# tmpsplit = re.sub('.', '', tmpsplit).strip()
# tmpsplit = re.sub('vol', '', tmpsplit.lower()).strip()
# #if vol label set as 'Vol 4' it will obliterate the Vol, but pass over the '4' - set
# #volfound to True so that it can loop back around.
# if not tmpsplit.isdigit():
# vol_label = tstsplit #store the wording of how the Vol is defined so we can skip it later on.
# volfound = True
# continue
# if len(tmpsplit[1:]) == 4 and tmpsplit[1:].isdigit(): #v2013
# logger.fdebug("[Vxxxx] Version detected as " + str(tmpsplit))
# vers4year = "yes" #re.sub("[^0-9]", " ", str(ct)) #remove the v
# elif len(tmpsplit[1:]) == 1 and tmpsplit[1:].isdigit(): #v2
# logger.fdebug("[Vx] Version detected as " + str(tmpsplit))
# vers4vol = str(tmpsplit)
# elif tmpsplit[1:].isdigit() and len(tmpsplit) < 4:
# logger.fdebug('[Vxxx] Version detected as ' +str(tmpsplit))
# vers4vol = str(tmpsplit)
# elif tmpsplit.isdigit() and len(tmpsplit) <=4:
# # this stuff is necessary for 32P volume manipulation
# if len(tmpsplit) == 4:
# vers4year = "yes"
# elif len(tmpsplit) == 1:
# vers4vol = str(tmpsplit)
# elif len(tmpsplit) < 4:
# vers4vol = str(tmpsplit)
# else:
# logger.fdebug("error - unknown length for : " + str(tmpsplit))
# continue
# else:
# logger.fdebug("error - unknown length for : " + str(tmpsplit))
# continue
logger.fdebug("volume detection commencing - adjusting length.")
logger.fdebug("volume detection commencing - adjusting length.")
logger.fdebug("watch comicversion is " + str(ComicVersion))
fndcomicversion = str(tstsplit)
logger.fdebug("version found: " + str(fndcomicversion))
logger.fdebug("vers4year: " + str(vers4year))
logger.fdebug("vers4vol: " + str(vers4vol))
logger.fdebug("watch comicversion is " + str(ComicVersion))
logger.fdebug("version found: " + str(fndcomicversion))
logger.fdebug("vers4year: " + str(vers4year))
logger.fdebug("vers4vol: " + str(vers4vol))
if vers4year is not "no" or vers4vol is not "no":
if vers4year is not "no" or vers4vol is not "no":
#if the volume is None, assume it's a V1 to increase % hits
if ComVersChk == 0:
D_ComicVersion = 1
else:
D_ComicVersion = ComVersChk
#if the volume is None, assume it's a V1 to increase % hits
if ComVersChk == 0:
D_ComicVersion = 1
else:
D_ComicVersion = ComVersChk
#if this is a one-off, SeriesYear will be None and cause errors.
if SeriesYear is None:
S_ComicVersion = 0
else:
S_ComicVersion = str(SeriesYear)
#if this is a one-off, SeriesYear will be None and cause errors.
if SeriesYear is None:
S_ComicVersion = 0
else:
S_ComicVersion = str(SeriesYear)
F_ComicVersion = re.sub("[^0-9]", "", fndcomicversion)
#if the found volume is a vol.0, up it to vol.1 (since there is no V0)
if F_ComicVersion == '0':
#need to convert dates to just be yyyy-mm-dd and do comparison, time operator in the below calc as well which probably throws off some accuracy.
if postdate_int >= issuedate_int and nzbprov == '32P':
logger.fdebug('32P torrent discovery. Store date (' + str(stdate) + ') is before posting date (' + str(pubdate) + '), forcing volume label to be the same as series label (0-Day Enforcement): v' + str(F_ComicVersion) + ' --> v' + str(S_ComicVersion))
F_ComicVersion = D_ComicVersion
else:
F_ComicVersion = '1'
F_ComicVersion = re.sub("[^0-9]", "", fndcomicversion)
#if the found volume is a vol.0, up it to vol.1 (since there is no V0)
if F_ComicVersion == '0':
#need to convert dates to just be yyyy-mm-dd and do comparison, time operator in the below calc as well which probably throws off some accuracy.
if postdate_int >= issuedate_int and nzbprov == '32P':
logger.fdebug('32P torrent discovery. Store date (' + str(stdate) + ') is before posting date (' + str(pubdate) + '), forcing volume label to be the same as series label (0-Day Enforcement): v' + str(F_ComicVersion) + ' --> v' + str(S_ComicVersion))
F_ComicVersion = D_ComicVersion
else:
F_ComicVersion = '1'
logger.fdebug("FCVersion: " + str(F_ComicVersion))
logger.fdebug("DCVersion: " + str(D_ComicVersion))
logger.fdebug("SCVersion: " + str(S_ComicVersion))
logger.fdebug("FCVersion: " + str(F_ComicVersion))
logger.fdebug("DCVersion: " + str(D_ComicVersion))
logger.fdebug("SCVersion: " + str(S_ComicVersion))
#here's the catch, sometimes annuals get posted as the Pub Year
# instead of the Series they belong to (V2012 vs V2013)
if annualize == "true" and int(ComicYear) == int(F_ComicVersion):
logger.fdebug("We matched on versions for annuals " + str(fndcomicversion))
scount+=1
cvers = "true"
#here's the catch, sometimes annuals get posted as the Pub Year
# instead of the Series they belong to (V2012 vs V2013)
if annualize == "true" and int(ComicYear) == int(F_ComicVersion):
logger.fdebug("We matched on versions for annuals " + str(fndcomicversion))
scount+=1
cvers = "true"
elif int(F_ComicVersion) == int(D_ComicVersion) or int(F_ComicVersion) == int(S_ComicVersion):
logger.fdebug("We matched on versions..." + str(fndcomicversion))
scount+=1
cvers = "true"
elif int(F_ComicVersion) == int(D_ComicVersion) or int(F_ComicVersion) == int(S_ComicVersion):
logger.fdebug("We matched on versions..." + str(fndcomicversion))
scount+=1
cvers = "true"
else:
logger.fdebug("Versions wrong. Ignoring possible match.")
scount = 0
cvers = "false"
else:
logger.fdebug("Versions wrong. Ignoring possible match.")
scount = 0
cvers = "false"
if cvers == "true":
#since we matched on versions, let's remove it entirely to improve matching.
logger.fdebug('Removing versioning from nzb filename to improve matching algorithims.')
cissb4vers = re.sub(tstsplit, "", comic_iss_b4).strip()
logger.fdebug('New b4split : ' + str(cissb4vers))
splitit = cissb4vers.split(None)
splitst -=1
break
if cvers == "true":
#since we matched on versions, let's remove it entirely to improve matching.
logger.fdebug('Removing versioning [' + fndcomicversion + '] from nzb filename to improve matching algorithims.')
cissb4vers = re.sub(fndcomicversion, "", comic_iss_b4).strip()
logger.fdebug('New b4split : ' + str(cissb4vers))
splitit = cissb4vers.split(None)
splitst -=1
#do an initial check
initialchk = 'ok'
@ -1623,7 +1648,7 @@ def nzbname_create(provider, title=None, info=None):
logger.fdebug('[SEARCHER] entry[title]: ' + title)
#gotta replace & or escape it
nzbname = re.sub("\&", 'and', title)
nzbname = re.sub('[\,\:\?\']', '', nzbname)
nzbname = re.sub('[\,\:\?\'\+]', '', nzbname)
nzbname = re.sub('[\(\)]', ' ', nzbname)
logger.fdebug('[SEARCHER] nzbname (remove chars): ' + nzbname)
nzbname = re.sub('.cbr', '', nzbname).strip()
@ -1797,17 +1822,17 @@ def searcher(nzbprov, nzbname, comicinfo, link, IssueID, ComicID, tmpprov, direc
#make sure the cache directory exists - if not, create it (used for storing nzbs).
if os.path.exists(mylar.CACHE_DIR):
logger.fdebug("Cache Directory successfully found at : " + mylar.CACHE_DIR)
pass
logger.fdebug("Cache Directory successfully found at : " + mylar.CACHE_DIR + ". Ensuring proper permissions.")
#enforce the permissions here to ensure the lower portion writes successfully
filechecker.setperms(mylar.CACHE_DIR, True)
else:
#let's make the dir.
logger.fdebug("Could not locate Cache Directory, attempting to create at : " + mylar.CACHE_DIR)
try:
os.makedirs(mylar.CACHE_DIR)
filechecker.validateAndCreateDirectory(mylar.CACHE_DIR, True)
logger.info("Temporary NZB Download Directory successfully created at: " + mylar.CACHE_DIR)
except OSError.e:
if e.errno != errno.EEXIST:
raise
except OSError:
raise
#save the nzb grabbed, so we can bypass all the 'send-url' crap.
if not nzbname.endswith('.nzb'):
@ -1860,7 +1885,6 @@ def searcher(nzbprov, nzbname, comicinfo, link, IssueID, ComicID, tmpprov, direc
except (OSError, IOError):
logger.warn('Failed to move nzb into blackhole directory - check blackhole directory and/or permissions.')
return "blackhole-fail"
logger.fdebug("filename saved to your blackhole as : " + nzbname)
logger.info(u"Successfully sent .nzb to your Blackhole directory : " + os.path.join(mylar.BLACKHOLE_DIR, nzbname))
sent_to = "your Blackhole Directory"
@ -1939,37 +1963,69 @@ def searcher(nzbprov, nzbname, comicinfo, link, IssueID, ComicID, tmpprov, direc
mylar.DOWNLOAD_APIKEY = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32]
#generate the mylar host address if applicable.
if mylar.ENABLE_HTTPS:
proto = 'https://'
else:
proto = 'http://'
if mylar.HTTP_ROOT is None:
hroot = '/'
elif mylar.HTTP_ROOT.endswith('/'):
hroot = mylar.HTTP_ROOT
else:
if mylar.HTTP_ROOT != '/':
hroot = mylar.HTTP_ROOT + '/'
else:
hroot = mylar.HTTP_ROOT
if mylar.LOCAL_IP is None:
#if mylar's local, get the local IP using socket.
try:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
mylar.LOCAL_IP = s.getsockname()[0]
s.close()
except:
logger.warn('Unable to determine local IP. Defaulting to host address for Mylar provided as : ' + str(mylar.HTTP_HOST))
if mylar.HOST_RETURN:
#from lib.pystun import as stun
#sip = '0.0.0.0'
#port = int(mylar.HTTP_PORT)
#try:
# nat_type, ext_ip, ext_port = stun.get_ip_info(sip,port)
#except:
# logger.warn('Unable to retrieve External IP.')
#mylar has the return value already provided (easier and will work if it's right)
if mylar.HOST_RETURN.endswith('/'):
mylar_host = mylar.HOST_RETURN
else:
mylar_host = mylar.HOST_RETURN + '/'
else:
if mylar.ENABLE_HTTPS:
proto = 'https://'
else:
proto = 'http://'
if mylar.HTTP_ROOT is None:
hroot = '/'
elif mylar.HTTP_ROOT.endswith('/'):
hroot = mylar.HTTP_ROOT
elif mylar.SAB_TO_MYLAR:
#if sab & mylar are on different machines, check to see if they are local or external IP's provided for host.
if mylar.HTTP_HOST == 'localhost' or mylar.HTTP_HOST == '0.0.0.0':
#if mylar's local, use the local IP already assigned to LOCAL_IP.
mylar_host = proto + str(mylar.LOCAL_IP) + ':' + str(mylar.HTTP_PORT) + hroot
else:
if mylar.HTTP_ROOT != '/':
hroot = mylar.HTTP_ROOT + '/'
if mylar.EXT_IP is None:
#if mylar isn't local, get the external IP using pystun.
import lib.pystun as stun
sip = mylar.HTTP_HOST
port = int(mylar.HTTP_PORT)
try:
nat_type, ext_ip, ext_port = stun.get_ip_info(sip,port)
mylar_host = proto + str(ext_ip) + ':' + str(mylar.HTTP_PORT) + hroot
mylar.EXT_IP = ext_ip
except:
logger.warn('Unable to retrieve External IP - try using the host_return option in the config.ini.')
mylar_host = proto + str(mylar.HTTP_HOST) + ':' + str(mylar.HTTP_PORT) + hroot
else:
hroot = mylar.HTTP_ROOT
mylar_host = proto + str(mylar.HTTP_HOST) + ':' + str(mylar.HTTP_PORT) + hroot
mylar_host = proto + str(mylar.EXT_IP) + ':' + str(mylar.HTTP_PORT) + hroot
else:
#if all else fails, drop it back to the basic host:port and try that.
if mylar.LOCAL_IP is None:
tmp_host = mylar.HTTP_HOST
else:
tmp_host = mylar.LOCAL_IP
mylar_host = proto + str(tmp_host) + ':' + str(mylar.HTTP_PORT) + hroot
fileURL = mylar_host + 'api?apikey=' + mylar.DOWNLOAD_APIKEY + '&cmd=downloadNZB&nzbname=' + nzbname
tmpapi = tmpapi + SABtype

View File

@ -34,7 +34,7 @@ import shutil
import mylar
from mylar import logger, db, importer, mb, search, filechecker, helpers, updater, parseit, weeklypull, PostProcessor, librarysync, moveit, Failed, readinglist #,rsscheck
from mylar import logger, db, importer, mb, search, filechecker, helpers, updater, parseit, weeklypull, PostProcessor, librarysync, moveit, Failed, readinglist, notifiers #,rsscheck
import lib.simplejson as simplejson
@ -145,16 +145,21 @@ class WebInterface(object):
}
usethefuzzy = comic['UseFuzzy']
skipped2wanted = "0"
if usethefuzzy is None: usethefuzzy = "0"
if usethefuzzy is None:
usethefuzzy = "0"
force_continuing = comic['ForceContinuing']
if force_continuing is None: force_continuing = 0
if force_continuing is None:
force_continuing = 0
if mylar.DELETE_REMOVE_DIR is None:
mylar.DELETE_REMOVE_DIR = 0
comicConfig = {
"comiclocation": mylar.COMIC_LOCATION,
"fuzzy_year0": helpers.radio(int(usethefuzzy), 0),
"fuzzy_year1": helpers.radio(int(usethefuzzy), 1),
"fuzzy_year2": helpers.radio(int(usethefuzzy), 2),
"skipped2wanted": helpers.checked(skipped2wanted),
"force_continuing": helpers.checked(force_continuing)
"force_continuing": helpers.checked(force_continuing),
"delete_dir": helpers.checked(mylar.DELETE_REMOVE_DIR)
}
if mylar.ANNUALS_ON:
annuals = myDB.select("SELECT * FROM annuals WHERE ComicID=?", [ComicID])
@ -762,38 +767,51 @@ class WebInterface(object):
logger.warn('Failed Download Handling is not enabled. Leaving Failed Download as-is.')
post_process.exposed = True
def pauseArtist(self, ComicID):
def pauseSeries(self, ComicID):
logger.info(u"Pausing comic: " + ComicID)
myDB = db.DBConnection()
controlValueDict = {'ComicID': ComicID}
newValueDict = {'Status': 'Paused'}
myDB.upsert("comics", newValueDict, controlValueDict)
raise cherrypy.HTTPRedirect("comicDetails?ComicID=%s" % ComicID)
pauseArtist.exposed = True
pauseSeries.exposed = True
def resumeArtist(self, ComicID):
def resumeSeries(self, ComicID):
logger.info(u"Resuming comic: " + ComicID)
myDB = db.DBConnection()
controlValueDict = {'ComicID': ComicID}
newValueDict = {'Status': 'Active'}
myDB.upsert("comics", newValueDict, controlValueDict)
raise cherrypy.HTTPRedirect("comicDetails?ComicID=%s" % ComicID)
resumeArtist.exposed = True
resumeSeries.exposed = True
def deleteArtist(self, ComicID):
def deleteSeries(self, ComicID, delete_dir=None):
print delete_dir
myDB = db.DBConnection()
comic = myDB.selectone('SELECT * from comics WHERE ComicID=?', [ComicID]).fetchone()
if comic['ComicName'] is None: ComicName = "None"
else: ComicName = comic['ComicName']
seriesdir = comic['ComicLocation']
logger.info(u"Deleting all traces of Comic: " + ComicName)
myDB.action('DELETE from comics WHERE ComicID=?', [ComicID])
myDB.action('DELETE from issues WHERE ComicID=?', [ComicID])
if mylar.ANNUALS_ON:
myDB.action('DELETE from annuals WHERE ComicID=?', [ComicID])
myDB.action('DELETE from upcoming WHERE ComicID=?', [ComicID])
if delete_dir: #mylar.DELETE_REMOVE_DIR:
logger.fdebug('Remove directory on series removal enabled.')
if os.path.exists(seriesdir):
logger.fdebug('Attempting to remove the directory and contents of : ' + seriesdir)
try:
shutil.rmtree(seriesdir)
except:
logger.warn('Unable to remove directory after removing series from Mylar.')
else:
logger.warn('Unable to remove directory as it does not exist in : ' + seriesdir)
helpers.ComicSort(sequence='update')
raise cherrypy.HTTPRedirect("home")
deleteArtist.exposed = True
deleteSeries.exposed = True
def wipenzblog(self, ComicID=None, IssueID=None):
myDB = db.DBConnection()
@ -4079,7 +4097,6 @@ class WebInterface(object):
group_metatag.exposed = True
def CreateFolders(self, createfolders=None):
print 'createfolders is ' + str(createfolders)
if createfolders:
mylar.CREATE_FOLDERS = int(createfolders)
mylar.config_write()
@ -4105,5 +4122,50 @@ class WebInterface(object):
syncfiles.exposed = True
def search_32p(self, search=None):
mylar.rsscheck.torrents(pickfeed='4', seriesname=search)
return mylar.rsscheck.torrents(pickfeed='4', seriesname=search)
search_32p.exposed = True
def testNMA(self):
nma = notifiers.NMA()
result = nma.test_notify()
if result:
return "Successfully sent NMA test - check to make sure it worked"
else:
return "Error sending test message to NMA"
testNMA.exposed = True
def testprowl(self):
prowl = notifiers.prowl()
result = prowl.test_notify()
if result:
return "Successfully sent Prowl test - check to make sure it worked"
else:
return "Error sending test message to Prowl"
testprowl.exposed = True
def testboxcar(self):
boxcar = notifiers.boxcar()
result = boxcar.test_notify()
if result:
return "Successfully sent Boxcar test - check to make sure it worked"
else:
return "Error sending test message to Boxcar"
testboxcar.exposed = True
def testpushover(self):
pushover = notifiers.pushover()
result = pushover.test_notify()
if result:
return "Successfully sent Pushover test - check to make sure it worked"
else:
return "Error sending test message to Pushover"
testpushover.exposed = True
def testpushbullet(self):
pushbullet = notifiers.pushbullet()
result = pushbullet.test_notify()
if result:
return "Successfully sent Pushbullet test - check to make sure it worked"
else:
return "Error sending test message to Pushbullet"
testpushbullet.exposed = True