Merge branch 'develop'

* develop:
  Release v0.5.1
  Prepare for next release
  Fixed HTML div closing tag. Updated titles
  Fixed typo
  Allow one to disable all tasks
  Allow one to adjust interval settings without restarting.
  Prevent infinite recursion while scanning library.
  Fix for new paths
  Added the real pkg_resources from setuptools 7.0. Should fix #2001
  Fixed contrib scripts. Added *.pyc cleaning scripts
  Check if pyOpenSSL is installed before enabling HTTPS.
  Ignore CSR files
  Fixed SSL too long errors in CherryPy.
  Spaces removal
  Add downgrade functionality (contributed script)
  Use urlparse to parse transmission URL
  Fix for #1998
This commit is contained in:
Bas Stottelaar
2014-11-13 20:00:27 +01:00
16 changed files with 3164 additions and 145 deletions

7
.gitignore vendored
View File

@@ -6,12 +6,13 @@
*.pyproj
*.sln
# Logs and databases #
# Headphones files #
######################
*.log
*.db*
*.db-journal
*.ini
version.lock
logs/*
cache/*
@@ -19,6 +20,7 @@ cache/*
##################
*.crt
*.key
*.csr
# OS generated files #
######################
@@ -64,6 +66,3 @@ _ReSharper*/
/logs
.project
.pydevproject
headphones_docs

View File

@@ -1,5 +1,18 @@
# Changelog
## v0.5.1
Released 13 November 2014
Highlights:
* Added: allow one to disable interval tasks (#2002)
* Added: script to downgrade Headphones to last version that started (Linux only)
* Fixed: SSL issues in CherryPy. Self-generated certificates will be 2048 now (#1995)
* Fixed: Transmission URL detection (#1998)
* Fixed: missing dependencies for APScheduler and CherryPy (#2001)
* Improved: symlink infinite recursion detection
The full list of commits can be found [here](https://github.com/rembo10/headphones/compare/v0.5...v0.5.1).
## v0.5
Released 10 November 2014

View File

@@ -9,7 +9,7 @@ In case you read this because you are posting an issue, please take a minute and
* Close your issue if you resolved it.
## For developers
If you think you can contribute code to the Headphones repository, do not hesitate to submit a pull request.
If you think you can contribute code to the Headphones repository, do not hesitate to submit a pull request.
### Branches
All pull requests should be based on the `develop` branch. When you want to develop a new feature, clone the repository with `git clone origin/develop -b FEATURE_NAME`. Use meaningful commit messages.
@@ -31,5 +31,5 @@ Altough Headphones did not adapt a code convention in the past, we try to follow
* `_private_something`
* `self.__really_private_field`
* `_global`
Document your code!

View File

@@ -164,6 +164,16 @@ def main():
else:
http_port = int(headphones.CONFIG.HTTP_PORT)
# Check if pyOpenSSL is installed. It is required for certificate generation
# and for CherryPy.
if headphones.CONFIG.ENABLE_HTTPS:
try:
import OpenSSL
except ImportError:
logger.warn("The pyOpenSSL module is missing. Install this " \
"module to enable HTTPS. HTTPS will be disabled.")
headphones.CONFIG.ENABLE_HTTPS = False
# Try to start the server. Will exit here is address is already in use.
web_config = {
'http_port': http_port,

9
contrib/clean_pyc.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
# Display information
echo "This script will remove *.pyc files. These files are generated by Python, but they can cause conflicts after an upgrade. It's safe to remove them, because they will be regenerated."
echo "Press enter to continue, or CTRL + C to quit."
read
# Remove the *.pyc
find "`dirname $0`/.." -type f -name "*.pyc" -exec rm -rf {} \;

32
contrib/downgrade.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
# Parameter check
if [ -z "$1" ]; then
echo "Syntax: $0 <data directory>"
exit 1
fi
# Version file check
if [ ! -s "$1/version.lock" ]; then
echo "Missing the version.lock file in the data folder, or the file is empty. Did you start Headphones at least once?"
exit 1
fi
# Git installation check
if [ ! -x "$(command -v git)" ]; then
echo "Git is required to downgrade."
exit 1
fi
# Display information
HASH=$(cat $1/version.lock)
echo "This script will try to downgrade Headphones to the last version that started, version $HASH. Make sure you have a backup of your config file and database, just in case!"
echo "Press enter to continue, or CTRL + C to quit."
read
# Downgrade
cd "`dirname $0`/.."
git reset --hard "$HASH"
echo "All done, Headphones should be downgraded to the last version that started."

View File

@@ -101,6 +101,7 @@
<fieldset>
<legend>Interval</legend>
<small>An interval of 0 will disable a task.</small>
<div class="row">
<label title="Time between two searches for new downloads.">
Search Interval
@@ -137,7 +138,7 @@
</tr>
<tr>
<div class="configmessage">
<i class="fa fa-info-circle"></i> Web Interface changes require a restart to take effect
<i class="fa fa-info-circle"></i> Web Interface changes require a restart to take effect. Saving settings will restart intervals.
</div>
</tr>
</table>
@@ -266,7 +267,7 @@
prio_force = ''
%>
<div class="row">
<label>NZBget Priority:</label>
<label>NZBget Priority</label>
<select name="nzbget_priority" id="nzbget_priority">
<option value="-100" ${prio_verylow}>Very Low</option>
<option value="-50" ${prio_low}>Low</option>
@@ -294,7 +295,7 @@
Music Download Directory:
</label>
<input type="text" name="download_dir" value="${config['download_dir']}" size="50">
<small>Full path where SAB or NZBget downloads your music. e.g. /Users/name/Downloads/music</small>
<small>Full path where SAB or NZBget downloads your music, e.g. /Users/name/Downloads/music</small>
</div>
<div class="row">
<label title="Number of days of retention your usenet provider provides.">
@@ -381,14 +382,14 @@
</fieldset>
<fieldset id="general_torrent_options">
<div class="row">
<label>Minimum seeders:</label>
<label>Minimum seeders</label>
<input type="text" name="numberofseeders" value="${config['numberofseeders']}" size="5">
<small>Number of minimum seeders a torrent must have to be accepted</small>
</div>
<div class="row">
<label>Music Download Directory</label>
<input type="text" name="download_torrent_dir" value="${config['download_torrent_dir']}" size="50">
<small>Full path where your torrent client downloads your music e.g. '/Users/name/Downloads/music'</small>
<small>Full path where your torrent client downloads your music e.g. /Users/name/Downloads/music.</small>
</div>
<div class="row checkbox">
<label>Keep Files for Seeding</label>
@@ -526,11 +527,12 @@
</div>
<div class="config">
<div class="row">
<label>Proxy URL (Optional): </label>
<label>Proxy URL</label>
<input type="text" name="piratebay_proxy_url" value="${config['piratebay_proxy_url']}" size="36">
<small>Optional. Leave empty for default.</small>
</div>
<div class="row">
<label>Seed Ratio: </label>
<label>Seed Ratio</label>
<input type="text" class="override-float" name="piratebay_ratio" value="${config['piratebay_ratio']}" size="10" title="Stop seeding when ratio met, 0 = unlimited. Scheduled job will remove torrent when post processed and finished seeding">
</div>
</div>
@@ -543,11 +545,12 @@
</div>
<div class="config">
<div class="row">
<label>Proxy URL (Optional): </label>
<label>Proxy URL</label>
<input type="text" name="kat_proxy_url" value="${config['kat_proxy_url']}" size="36">
<small>Optional. Leave empty for default.</small>
</div>
<div class="row">
<label>Seed Ratio: </label>
<label>Seed Ratio</label>
<input type="text" class="override-float" name="kat_ratio" value="${config['kat_ratio']}" size="10" title="Stop seeding when ratio met, 0 = unlimited. Scheduled job will remove torrent when post processed and finished seeding">
</div>
</div>
@@ -560,15 +563,15 @@
</div>
<div class="config">
<div class="row">
<label>UID Number: </label>
<label>UID Number</label>
<input type="text" name="waffles_uid" value="${config['waffles_uid']}" size="36">
</div>
<div class="row">
<label>Passkey: </label>
<label>Passkey</label>
<input type="password" name="waffles_passkey" value="${config['waffles_passkey']}" size="36">
</div>
<div class="row">
<label>Seed Ratio: </label>
<label>Seed Ratio</label>
<input type="text" class="override-float" name="waffles_ratio" value="${config['waffles_ratio']}" size="10" title="Stop seeding when ratio met, 0 = unlimited. Scheduled job will remove torrent when post processed and finished seeding">
</div>
</div>
@@ -581,15 +584,15 @@
</div>
<div class="config">
<div class="row">
<label>User Name: </label>
<label>Userame</label>
<input type="text" name="rutracker_user" value="${config['rutracker_user']}" size="36">
</div>
<div class="row">
<label>Password: </label>
<label>Password</label>
<input type="password" name="rutracker_password" value="${config['rutracker_password']}" size="36">
</div>
<div class="row">
<label>Seed Ratio: </label>
<label>Seed Ratio</label>
<input type="text" class="override-float" name="rutracker_ratio" value="${config['rutracker_ratio']}" size="10" title="Stop seeding when ratio met, 0 = unlimited. Scheduled job will remove torrent when post processed and finished seeding">
</div>
</div>
@@ -688,7 +691,7 @@
</fieldset>
<fieldset>
<legend>Search Words</legend>
<small>Separate words with a comma, e.g. "word1,word2,word3"</small>
<small>Separate words with a comma, e.g. "word1,word2,word3".</small>
<div class="row">
<label>Ignored Words</label>
<input type="text" name="ignored_words" value="${config['ignored_words']}" size="50">
@@ -714,6 +717,7 @@
Split single file albums into multiple tracks
<input type="checkbox" name="cue_split" id="cue_split" value="1" ${config['cue_split']} />
</label>
</div>
<div class="row checkbox left clearfix">
<label title="Freeze the database, so new artists won't be added automatically. Use this if Headphones adds artists because due to wrong snatches. This check is skipped when the folder name is appended with release group ID.">
Freeze database for adding new artist
@@ -767,14 +771,18 @@
</label>
</div>
<div class="row">
<label>Path to Destination Folder</label>
<label>
Destination Directory:
</label>
<input type="text" name="destination_dir" value="${config['destination_dir']}" size="50">
<small>e.g. /Users/name/Music/iTunes or /Volumes/share/music</small>
<small>The directory where Headphones will move file to after post processing, e.g. /Volumes/share/music.</small>
</div>
<div class="row">
<label>Path to Lossless Destination folder (optional)</label>
<label>
Lossless Destination Directory:
</label>
<input type="text" name="lossless_destination_dir" value="${config['lossless_destination_dir']}" size="50">
<small>Set this if you have a separate directory for lossless music</small>
<small>Optional. Set this if you have a separate directory for lossless music.</small>
</div>
</fieldset>
</td>
@@ -816,7 +824,7 @@
<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>
<label>Priority (-2,-1,0,1 or 2)</label>
<input type="text" name="prowl_priority" value="${config['prowl_priority']}" size="2">
</div>
</div>
@@ -991,11 +999,11 @@
<input type="checkbox" name="pushover_onsnatch" value="1" ${config['pushover_onsnatch']} /><label>Notify on snatch?</label>
</div>
<div class="row">
<label>Priority (-1,0, or 1):</label>
<label>Priority (-1,0, or 1)</label>
<input type="text" name="pushover_priority" value="${config['pushover_priority']}" size="2">
</div>
<div class="row">
<label>API Token (leave blank to use Headphones default):</label><input type="text" name="pushover_apitoken" value="${config['pushover_apitoken']}" size="50">
<label>API Token (leave blank to use Headphones default)</label><input type="text" name="pushover_apitoken" value="${config['pushover_apitoken']}" size="50">
</div>
</div>
</fieldset>
@@ -1163,7 +1171,7 @@
</div>
<div class="row">
<div class="row">
<label>Multi-core count:</label>
<label>Multi-core count</label>
<input type="text" name="encoder_multicore_count" value="${config['encoder_multicore_count']}" size="7">
<small>Set equal to the number of cores, or 0 for auto</small>
</div>
@@ -1307,15 +1315,15 @@
<label>Automatically mark manually added albums as wanted</label>
</div>
<div class="row">
<label>Folder Permissions:</label>
<label>Folder Permissions</label>
<input type="text" name="folder_permissions" value="${config['folder_permissions']}" size="7">
</div>
<div class="row">
<label>File Permissions:</label>
<label>File Permissions</label>
<input type="text" name="file_permissions" value="${config['file_permissions']}" size="7">
</div>
<div class="row">
<label>Cache Size (in MB):</label>
<label>Cache Size (in MB)</label>
<input type="text" name="cache_sizemb" value="${config['cache_sizemb']}" size="7">
</div>
</fieldset>
@@ -1336,11 +1344,11 @@
</select>
</div>
<div class="row">
<label>Log Directory:</label>
<label>Log Directory</label>
<input type="text" name="log_dir" value="${config['log_dir']}" size="50">
</div>
<div class="row">
<label>Cache Directory:</label>
<label>Cache Directory</label>
<input type="text" name="cache_dir" value="${config['cache_dir']}" size="50">
</div>
</fieldset>
@@ -1352,7 +1360,7 @@
</div>
<div id="songkickoptions">
<div class="row">
<label>API Key:</label>
<label>API Key</label>
<input type="text" name="songkick_apikey" value="${config['songkick_apikey']}" size="50">
</div>
<div class="row checkbox">
@@ -1360,7 +1368,7 @@
</div>
<div id="songkick_filteroptions">
<div class="row">
<label>Metro Area ID:</label>
<label>Metro Area ID</label>
<input type="text" name="songkick_location" value="${config['songkick_location']}" size="10" title="Enter the Metro Area ID, e.g. the ID for London is 24426, this can be found by clicking the link and searching/selecting the city, e.g. London should find http://www.songkick.com/metro_areas/24426-uk-london">
<a target="_blank" href="http://www.songkick.com/developer/location-search">Find Area ID</a>
</div>

View File

@@ -64,6 +64,7 @@ CREATEPID = False
PIDFILE = None
SCHED = BackgroundScheduler()
SCHED_LOCK = threading.Lock()
INIT_LOCK = threading.Lock()
_INITIALIZED = False
@@ -138,13 +139,12 @@ def initialize(config_file):
if not os.path.exists(CONFIG.CACHE_DIR):
try:
os.makedirs(CONFIG.CACHE_DIR)
except OSError:
logger.error(
'Could not create cache dir. Check permissions of datadir: %s', DATA_DIR)
except OSError as e:
logger.error("Could not create cache dir '%s': %s", DATA_DIR, e)
# Sanity check for search interval. Set it to at least 6 hours
if CONFIG.SEARCH_INTERVAL < 360:
logger.info("Search interval too low. Resetting to 6 hour minimum")
if CONFIG.SEARCH_INTERVAL and CONFIG.SEARCH_INTERVAL < 360:
logger.info("Search interval too low. Resetting to 6 hour minimum.")
CONFIG.SEARCH_INTERVAL = 360
# Initialize the database
@@ -154,10 +154,23 @@ def initialize(config_file):
except Exception as e:
logger.error("Can't connect to the database: %s", e)
# Get the currently installed version - returns None, 'win32' or the git hash
# Also sets INSTALL_TYPE variable to 'win', 'git' or 'source'
# Get the currently installed version. Returns None, 'win32' or the git
# hash.
CURRENT_VERSION, CONFIG.GIT_BRANCH = versioncheck.getVersion()
# Write current version to a file, so we know which version did work.
# This allowes one to restore to that version. The idea is that if we
# arrive here, most parts of Headphones seem to work.
if CURRENT_VERSION:
version_lock_file = os.path.join(DATA_DIR, "version.lock")
try:
with open(version_lock_file, "w") as fp:
fp.write(CURRENT_VERSION)
except IOError as e:
logger.error("Unable to write current version to file '%s': %s",
version_lock_file, e)
# Check for new versions
if CONFIG.CHECK_GITHUB_ON_STARTUP:
try:
@@ -244,36 +257,60 @@ def launch_browser(host, port, root):
logger.error('Could not launch browser: %s', e)
def initialize_scheduler():
"""
Start the scheduled background tasks. Because this method can be called
multiple times, the old tasks will be first removed.
"""
from headphones import updater, searcher, librarysync, postprocessor, \
torrentfinished
with SCHED_LOCK:
# Remove all jobs
count = len(SCHED.get_jobs())
if count > 0:
logger.debug("Current number of background tasks: %d", count)
SCHED.shutdown()
SCHED.remove_all_jobs()
# Regular jobs
if CONFIG.UPDATE_DB_INTERVAL > 0:
SCHED.add_job(updater.dbUpdate, trigger=IntervalTrigger(
hours=CONFIG.UPDATE_DB_INTERVAL))
if CONFIG.SEARCH_INTERVAL > 0:
SCHED.add_job(searcher.searchforalbum, trigger=IntervalTrigger(
minutes=CONFIG.SEARCH_INTERVAL))
if CONFIG.LIBRARYSCAN_INTERVAL > 0:
SCHED.add_job(librarysync.libraryScan, trigger=IntervalTrigger(
hours=CONFIG.LIBRARYSCAN_INTERVAL))
if CONFIG.DOWNLOAD_SCAN_INTERVAL > 0:
SCHED.add_job(postprocessor.checkFolder, trigger=IntervalTrigger(
minutes=CONFIG.DOWNLOAD_SCAN_INTERVAL))
# Update check
if CONFIG.CHECK_GITHUB:
SCHED.add_job(versioncheck.checkGithub, trigger=IntervalTrigger(
minutes=CONFIG.CHECK_GITHUB_INTERVAL))
# Remove Torrent + data if Post Processed and finished Seeding
if CONFIG.TORRENT_REMOVAL_INTERVAL > 0:
SCHED.add_job(torrentfinished.checkTorrentFinished,
trigger=IntervalTrigger(
minutes=CONFIG.TORRENT_REMOVAL_INTERVAL))
# Start scheduler
logger.info("(Re-)Scheduling background tasks")
SCHED.start()
def start():
global started
if _INITIALIZED:
# Start our scheduled background tasks
from headphones import updater, searcher, librarysync, postprocessor, torrentfinished
SCHED.add_job(updater.dbUpdate, trigger=IntervalTrigger(
hours=CONFIG.UPDATE_DB_INTERVAL))
SCHED.add_job(searcher.searchforalbum, trigger=IntervalTrigger(
minutes=CONFIG.SEARCH_INTERVAL))
SCHED.add_job(librarysync.libraryScan, trigger=IntervalTrigger(
hours=CONFIG.LIBRARYSCAN_INTERVAL))
if CONFIG.CHECK_GITHUB:
SCHED.add_job(versioncheck.checkGithub, trigger=IntervalTrigger(
minutes=CONFIG.CHECK_GITHUB_INTERVAL))
if CONFIG.DOWNLOAD_SCAN_INTERVAL > 0:
SCHED.add_job(postprocessor.checkFolder, trigger=IntervalTrigger(
minutes=CONFIG.DOWNLOAD_SCAN_INTERVAL))
# Remove Torrent + data if Post Processed and finished Seeding
if CONFIG.TORRENT_REMOVAL_INTERVAL > 0:
SCHED.add_job(torrentfinished.checkTorrentFinished, trigger=IntervalTrigger(
minutes=CONFIG.TORRENT_REMOVAL_INTERVAL))
SCHED.start()
initialize_scheduler()
started = True

View File

@@ -604,6 +604,43 @@ def smartMove(src, dest, delete=True):
except Exception as e:
logger.warn('Error moving file %s: %s', filename.decode(headphones.SYS_ENCODING, 'replace'), e)
def walk_directory(basedir, followlinks=True):
"""
Enhanced version of 'os.walk' where symlink directores are traversed, but
with care. In case a folder is already processed, don't traverse it again.
"""
import logger
# Add the base path, because symlinks poiting to the basedir should not be
# traversed again.
traversed = [os.path.abspath(basedir)]
def _inner(root, directories, files):
for directory in directories:
path = os.path.join(root, directory)
if followlinks and os.path.islink(path):
real_path = os.path.abspath(os.readlink(path))
if real_path in traversed:
logger.debug("Skipping '%s' since it is a symlink to "\
"'%s', which is already visited.", path, real_path)
else:
traversed.append(real_path)
for args in os.walk(real_path):
for result in _inner(*args):
yield result
# Pass on actual result
yield root, directories, files
# Start traversing
for args in os.walk(basedir):
for result in _inner(*args):
yield result
#########################
#Sab renaming functions #
#########################
@@ -660,44 +697,41 @@ def sab_sanitize_foldername(name):
return name
def split_string(mystring, splitvar=','):
mylist = []
for each_word in mystring.split(splitvar):
mylist.append(each_word.strip())
return mylist
def create_https_certificates(ssl_cert, ssl_key):
"""
Stolen from SickBeard (http://github.com/midgetspy/Sick-Beard):
Create self-signed HTTPS certificares and store in paths 'ssl_cert' and 'ssl_key'
Create a pair of self-signed HTTPS certificares and store in them in
'ssl_cert' and 'ssl_key'. Method assumes pyOpenSSL is installed.
This code is stolen from SickBeard (http://github.com/midgetspy/Sick-Beard).
"""
from headphones import logger
try:
from OpenSSL import crypto
from certgen import createKeyPair, createCertRequest, createCertificate, TYPE_RSA, serial
except:
logger.warn("pyOpenSSL module missing, please install to enable HTTPS")
return False
from OpenSSL import crypto
from certgen import createKeyPair, createCertRequest, createCertificate, \
TYPE_RSA, serial
# Create the CA Certificate
cakey = createKeyPair(TYPE_RSA, 1024)
careq = createCertRequest(cakey, CN='Certificate Authority')
cakey = createKeyPair(TYPE_RSA, 2048)
careq = createCertRequest(cakey, CN="Certificate Authority")
cacert = createCertificate(careq, (careq, cakey), serial, (0, 60 * 60 * 24 * 365 * 10)) # ten years
cname = 'Headphones'
pkey = createKeyPair(TYPE_RSA, 1024)
req = createCertRequest(pkey, CN=cname)
pkey = createKeyPair(TYPE_RSA, 2048)
req = createCertRequest(pkey, CN="Headphones")
cert = createCertificate(req, (cacert, cakey), serial, (0, 60 * 60 * 24 * 365 * 10)) # ten years
# Save the key and certificate to disk
try:
with open(ssl_key, 'w') as f:
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
with open(ssl_cert, 'w') as f:
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
with open(ssl_key, "w") as fp:
fp.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
with open(ssl_cert, "w") as fp:
fp.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
except IOError as e:
logger.error("Error creating SSL key and certificate: %s", e)
return False

View File

@@ -78,7 +78,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
latest_subdirectory = []
for r, d, f in os.walk(dir, followlinks=True):
for r, d, f in helpers.walk_directory(dir):
# Need to abuse slicing to get a copy of the list, doing it directly
# will skip the element after a deleted one using a list comprehension
# will not work correctly for nested subdirectories (os.walk keeps its

View File

@@ -17,6 +17,7 @@ from headphones import logger, helpers, common, request
from xml.dom import minidom
from httplib import HTTPSConnection
from urlparse import parse_qsl
from urllib import urlencode
from pynma import pynma
@@ -33,12 +34,6 @@ import json
import oauth2 as oauth
import pythontwitter as twitter
try:
from urlparse import parse_qsl
except ImportError:
from cgi import parse_qsl
class GROWL(object):
"""
Growl notifications, for OS X.

View File

@@ -18,6 +18,7 @@ from headphones import logger, request
import time
import json
import base64
import urlparse
import headphones
# This is just a simple script to send torrents to transmission. The
@@ -126,7 +127,6 @@ def torrentAction(method, arguments):
host = headphones.CONFIG.TRANSMISSION_HOST
username = headphones.CONFIG.TRANSMISSION_USERNAME
password = headphones.CONFIG.TRANSMISSION_PASSWORD
sessionid = None
if not host.startswith('http'):
host = 'http://' + host
@@ -134,30 +134,22 @@ def torrentAction(method, arguments):
if host.endswith('/'):
host = host[:-1]
# Either the host ends with a port, or some directory, or rpc
# If it ends with /rpc we don't have to do anything
# If it ends with a port we add /transmission/rpc
# anything else we just add rpc
if not host.endswith('/rpc'):
# Check if it ends in a port number
i = host.rfind(':')
if i >= 0:
possible_port = host[i + 1:]
host = host + "/rpc"
try:
port = int(possible_port)
if port:
host = host + "/transmission/rpc"
except ValueError:
logger.debug('No port, assuming not transmission')
else:
logger.error('Transmission port missing')
return
# Fix the URL. We assume that the user does not point to the RPC endpoint,
# so add it if it is missing.
parts = list(urlparse.urlparse(host))
if not parts[0] in ("http", "https"):
parts[0] = "http"
if not parts[2].endswith("/rpc"):
parts[2] += "/transmission/rpc"
host = urlparse.urlunparse(parts)
# Retrieve session id
auth = (username, password) if username and password else None
response = request.request_response(host, auth=auth, whitelist_status_code=[401, 409])
response = request.request_response(host, auth=auth,
whitelist_status_code=[401, 409])
if response is None:
logger.error("Error gettings Transmission session ID")
@@ -166,26 +158,30 @@ def torrentAction(method, arguments):
# Parse response
if response.status_code == 401:
if auth:
logger.error("Username and/or password not accepted by Transmission")
logger.error("Username and/or password not accepted by " \
"Transmission")
else:
logger.error("Transmission authorization required")
return
elif response.status_code == 409:
sessionid = response.headers['x-transmission-session-id']
session_id = response.headers['x-transmission-session-id']
if not sessionid:
logger.error("Error getting Session ID from Transmission")
return
if not session_id:
logger.error("Expected a Session ID from Transmission")
return
# Prepare next request
headers = {'x-transmission-session-id': sessionid}
headers = {'x-transmission-session-id': session_id}
data = {'method': method, 'arguments': arguments}
response = request.request_json(host, method="post", data=json.dumps(data), headers=headers, auth=auth)
response = request.request_json(host, method="POST", data=json.dumps(data),
headers=headers, auth=auth)
print response
if not response:
logger.error("Error sending torrent to Transmission")
return
return response
return response

View File

@@ -1241,14 +1241,17 @@ class WebInterface(object):
headphones.CONFIG.add_extra_newznab(extra_newznab)
# Sanity checking
if headphones.CONFIG.SEARCH_INTERVAL < 360:
if headphones.CONFIG.SEARCH_INTERVAL and headphones.CONFIG.SEARCH_INTERVAL < 360:
logger.info("Search interval too low. Resetting to 6 hour minimum")
headphones.CONFIG.SEARCH_INTERVAL = 360
# Write the config
headphones.CONFIG.write()
#reconfigure musicbrainz database connection with the new values
# Reconfigure scheduler
headphones.initialize_scheduler()
# Reconfigure musicbrainz database connection with the new values
mb.startmb()
raise cherrypy.HTTPRedirect("config")

View File

@@ -23,9 +23,7 @@ from headphones.webserve import WebInterface
from headphones.helpers import create_https_certificates
def initialize(options=None):
if options is None:
options = {}
def initialize(options):
# HTTPS stuff stolen from sickbeard
enable_https = options['enable_https']
@@ -33,16 +31,17 @@ def initialize(options=None):
https_key = options['https_key']
if enable_https:
# If either the HTTPS certificate or key do not exist, make some self-signed ones.
# If either the HTTPS certificate or key do not exist, try to make
# self-signed ones.
if not (https_cert and os.path.exists(https_cert)) or not (https_key and os.path.exists(https_key)):
if not create_https_certificates(https_cert, https_key):
logger.warn(u"Unable to create cert/key files, disabling HTTPS")
headphones.CONFIG.ENABLE_HTTPS = False
logger.warn("Unable to create certificate and key. Disabling " \
"HTTPS")
enable_https = False
if not (os.path.exists(https_cert) and os.path.exists(https_key)):
logger.warn(u"Disabled HTTPS because of missing CERT and KEY files")
headphones.CONFIG.ENABLE_HTTPS = False
logger.warn("Disabled HTTPS because of missing certificate and " \
"key.")
enable_https = False
options_dict = {

View File

@@ -1324,8 +1324,8 @@ class HTTPConnection(object):
def __init__(self, server, sock, makefile=CP_fileobject):
self.server = server
self.socket = sock
self.rfile = makefile(sock._sock, "rb", self.rbufsize)
self.wfile = makefile(sock._sock, "wb", self.wbufsize)
self.rfile = makefile(sock, "rb", self.rbufsize)
self.wfile = makefile(sock, "wb", self.wbufsize)
self.requests_seen = 0
def communicate(self):

File diff suppressed because it is too large Load Diff