diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py
index 4ad37d6..9647d95 100644
--- a/couchpotato/core/_base/_core/main.py
+++ b/couchpotato/core/_base/_core/main.py
@@ -124,7 +124,7 @@ class Core(Plugin):
time.sleep(1)
- log.debug('Save to shutdown/restart')
+ log.debug('Safe to shutdown/restart')
try:
IOLoop.current().stop()
diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py
index 900fd8c..adbfb7e 100644
--- a/couchpotato/core/downloaders/base.py
+++ b/couchpotato/core/downloaders/base.py
@@ -39,6 +39,8 @@ class Downloader(Provider):
addEvent('download.enabled_types', self.getEnabledDownloadType)
addEvent('download.status', self._getAllDownloadStatus)
addEvent('download.remove_failed', self._removeFailed)
+ addEvent('download.pause', self._pause)
+ addEvent('download.process_complete', self._processComplete)
def getEnabledDownloadType(self):
for download_type in self.type:
@@ -65,14 +67,30 @@ class Downloader(Provider):
if self.isDisabled(manual = True, data = {}):
return
- if self.conf('delete_failed', default = True):
- return self.removeFailed(item)
+ if item and item.get('downloader') == self.getName():
+ if self.conf('delete_failed'):
+ return self.removeFailed(item)
- return False
+ return False
+ return
def removeFailed(self, item):
return
+ def _processComplete(self, item):
+ if self.isDisabled(manual = True, data = {}):
+ return
+
+ if item and item.get('downloader') == self.getName():
+ if self.conf('remove_complete'):
+ return self.processComplete(item = item, delete_files = self.conf('delete_files', default = False))
+
+ return False
+ return
+
+ def processComplete(self, item, delete_files):
+ return
+
def isCorrectType(self, item_type):
is_correct = item_type in self.type
@@ -124,6 +142,17 @@ class Downloader(Provider):
((d_manual and manual) or (d_manual is False)) and \
(not data or self.isCorrectType(data.get('type')))
+ def _pause(self, item, pause = True):
+ if self.isDisabled(manual = True, data = {}):
+ return
+
+ if item and item.get('downloader') == self.getName():
+ self.pause(item, pause)
+ return True
+ return
+
+ def pause(self, item, pause):
+ return
class StatusList(list):
diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py
index f17db9c..8e132b7 100644
--- a/couchpotato/core/downloaders/sabnzbd/__init__.py
+++ b/couchpotato/core/downloaders/sabnzbd/__init__.py
@@ -42,6 +42,13 @@ config = [{
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
},
{
+ 'name': 'remove_complete',
+ 'label': 'Remove NZB',
+ 'default': True,
+ 'type': 'bool',
+ 'description': 'Remove the NZB from history after it completed.',
+ },
+ {
'name': 'delete_failed',
'default': True,
'type': 'bool',
diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py
index f2f217a..56e9a21 100644
--- a/couchpotato/core/downloaders/sabnzbd/main.py
+++ b/couchpotato/core/downloaders/sabnzbd/main.py
@@ -129,6 +129,22 @@ class Sabnzbd(Downloader):
return True
+ def processComplete(self, item, delete_files = False):
+ log.debug('Requesting SabNZBd to remove the NZB %s%s.', (item['name']))
+
+ try:
+ self.call({
+ 'mode': 'history',
+ 'name': 'delete',
+ 'del_files': '0',
+ 'value': item['id']
+ }, use_json = False)
+ except:
+ log.error('Failed removing: %s', traceback.format_exc(0))
+ return False
+
+ return True
+
def call(self, request_params, use_json = True, **kwargs):
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(request_params, {
diff --git a/couchpotato/core/downloaders/transmission/__init__.py b/couchpotato/core/downloaders/transmission/__init__.py
index 6dfbd3f..bca7eae 100644
--- a/couchpotato/core/downloaders/transmission/__init__.py
+++ b/couchpotato/core/downloaders/transmission/__init__.py
@@ -44,18 +44,32 @@ config = [{
'description': 'Download to this directory. Keep empty for default Transmission download directory.',
},
{
- 'name': 'ratio',
- 'default': 10,
- 'type': 'float',
+ 'name': 'seeding',
+ 'label': 'Seeding support',
+ 'default': True,
+ 'type': 'bool',
+ 'description': '(Hard)link/copy after download is complete (if enabled in renamer), wait for seeding to finish before (re)moving and set the seeding goal from the torrent providers.',
+ },
+ {
+ 'name': 'remove_complete',
+ 'label': 'Remove torrent',
+ 'default': True,
+ 'type': 'bool',
+ 'description': 'Remove the torrent from Transmission after it finished seeding.',
+ },
+ {
+ 'name': 'delete_files',
+ 'label': 'Remove files',
+ 'default': True,
+ 'type': 'bool',
'advanced': True,
- 'description': 'Stop transfer when reaching ratio',
+ 'description': 'Also remove the leftover files.',
},
{
- 'name': 'ratiomode',
- 'default': 0,
- 'type': 'int',
- 'advanced': True,
- 'description': '0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.',
+ 'name': 'paused',
+ 'type': 'bool',
+ 'default': False,
+ 'description': 'Add the torrent paused.',
},
{
'name': 'manual',
@@ -64,6 +78,18 @@ config = [{
'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
},
+ {
+ 'name': 'stalled_as_failed',
+ 'default': True,
+ 'type': 'bool',
+ 'description': 'Consider a stalled torrent as failed',
+ },
+ {
+ 'name': 'delete_failed',
+ 'default': True,
+ 'type': 'bool',
+ 'description': 'Delete a release after the download has failed.',
+ },
],
}
],
diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py
index 6094e89..46b9541 100644
--- a/couchpotato/core/downloaders/transmission/main.py
+++ b/couchpotato/core/downloaders/transmission/main.py
@@ -1,6 +1,7 @@
from base64 import b64encode
from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import isInt
+from couchpotato.core.helpers.variable import tryInt, tryFloat
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
from datetime import timedelta
@@ -8,7 +9,6 @@ import httplib
import json
import os.path
import re
-import traceback
import urllib2
log = CPLog(__name__)
@@ -18,144 +18,129 @@ class Transmission(Downloader):
type = ['torrent', 'torrent_magnet']
log = CPLog(__name__)
-
- def download(self, data, movie, filedata = None):
-
- log.info('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
-
+ trpc = None
+
+ def connect(self):
# Load host from config and split out port.
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
+ if not self.trpc:
+ self.trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
+ return self.trpc
- # Set parameters for Transmission
- params = {
- 'paused': self.conf('paused', default = 0),
- }
+ def download(self, data, movie, filedata = None):
- if len(self.conf('directory', default = '')) > 0:
- folder_name = self.createFileName(data, filedata, movie)[:-len(data.get('type')) - 1]
- params['download-dir'] = os.path.join(self.conf('directory', default = ''), folder_name).rstrip(os.path.sep)
+ log.info('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
- torrent_params = {}
- if self.conf('ratio'):
- torrent_params = {
- 'seedRatioLimit': self.conf('ratio'),
- 'seedRatioMode': self.conf('ratiomode')
- }
+ if not self.connect():
+ return False
if not filedata and data.get('type') == 'torrent':
log.error('Failed sending torrent, no data')
return False
- # Send request to Transmission
- try:
- trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
- if data.get('type') == 'torrent_magnet':
- remote_torrent = trpc.add_torrent_uri(data.get('url'), arguments = params)
- torrent_params['trackerAdd'] = self.torrent_trackers
+ # Set parameters for adding torrent
+ params = {}
+ params['paused'] = self.conf('paused', default = False)
+
+ if self.conf('directory'):
+ if os.path.isdir(self.conf('directory')):
+ params['download-dir'] = self.conf('directory')
else:
- remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params)
+ log.error('Download directory from Transmission settings: %s doesn\'t exist', self.conf('directory'))
- if not remote_torrent:
- return False
+ # Change parameters of torrent
+ torrent_params = {}
+ if data.get('seed_ratio') and self.conf('seeding'):
+ torrent_params['seedRatioLimit'] = tryFloat(data.get('seed_ratio'))
+ torrent_params['seedRatioMode'] = 1
- # Change settings of added torrents
- elif torrent_params:
- trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
+ if data.get('seed_time') and self.conf('seeding'):
+ torrent_params['seedIdleLimit'] = tryInt(data.get('seed_time'))*60
+ torrent_params['seedIdleMode'] = 1
- log.info('Torrent sent to Transmission successfully.')
- return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
- except:
- log.error('Failed to change settings for transfer: %s', traceback.format_exc())
+ # Send request to Transmission
+ if data.get('type') == 'torrent_magnet':
+ remote_torrent = self.trpc.add_torrent_uri(data.get('url'), arguments = params)
+ torrent_params['trackerAdd'] = self.torrent_trackers
+ else:
+ remote_torrent = self.trpc.add_torrent_file(b64encode(filedata), arguments = params)
+
+ if not remote_torrent:
+ log.error('Failed sending torrent to Transmission')
return False
+ # Change settings of added torrents
+ if torrent_params:
+ self.trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
+
+ log.info('Torrent sent to Transmission successfully.')
+ return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
+
def getAllDownloadStatus(self):
log.debug('Checking Transmission download status.')
- # Load host from config and split out port.
- host = self.conf('host').split(':')
- if not isInt(host[1]):
- log.error('Config properties are not filled in correctly, port is missing.')
- return False
-
- # Go through Queue
- try:
- trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
- return_params = {
- 'fields': ['id', 'name', 'hashString', 'percentDone', 'status', 'eta', 'isFinished', 'downloadDir', 'uploadRatio']
- }
- queue = trpc.get_alltorrents(return_params)
- except Exception, err:
- log.error('Failed getting queue: %s', err)
+ if not self.connect():
return False
- if not queue:
- return []
-
statuses = StatusList(self)
- # Get torrents status
- # CouchPotato Status
- #status = 'busy'
- #status = 'failed'
- #status = 'completed'
- # Transmission Status
- #status = 0 => "Torrent is stopped"
- #status = 1 => "Queued to check files"
- #status = 2 => "Checking files"
- #status = 3 => "Queued to download"
- #status = 4 => "Downloading"
- #status = 4 => "Queued to seed"
- #status = 6 => "Seeding"
- #To do :
- # add checking file
- # manage no peer in a range time => fail
+ return_params = {
+ 'fields': ['id', 'name', 'hashString', 'percentDone', 'status', 'eta', 'isStalled', 'isFinished', 'downloadDir', 'uploadRatio', 'secondsSeeding', 'seedIdleLimit']
+ }
+
+ queue = self.trpc.get_alltorrents(return_params)
+ if not (queue and queue.get('torrents')):
+ log.debug('Nothing in queue or error')
+ return False
for item in queue['torrents']:
- log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / eta=%s / uploadRatio=%s / confRatio=%s / isFinished=%s', (item['name'], item['id'], item['downloadDir'], item['hashString'], item['percentDone'], item['status'], item['eta'], item['uploadRatio'], self.conf('ratio'), item['isFinished']))
+ log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / eta=%s / uploadRatio=%s / isFinished=%s', (item['name'], item['id'], item['downloadDir'], item['hashString'], item['percentDone'], item['status'], item['eta'], item['uploadRatio'], item['isFinished']))
if not os.path.isdir(Env.setting('from', 'renamer')):
log.error('Renamer "from" folder doesn\'t to exist.')
return
- if (item['percentDone'] * 100) >= 100 and (item['status'] == 6 or item['status'] == 0) and item['uploadRatio'] > self.conf('ratio'):
- try:
- trpc.stop_torrent(item['hashString'], {})
- statuses.append({
- 'id': item['hashString'],
- 'name': item['name'],
- 'status': 'completed',
- 'original_status': item['status'],
- 'timeleft': str(timedelta(seconds = 0)),
- 'folder': os.path.join(item['downloadDir'], item['name']),
- })
- except Exception, err:
- log.error('Failed to stop and remove torrent "%s" with error: %s', (item['name'], err))
- statuses.append({
- 'id': item['hashString'],
- 'name': item['name'],
- 'status': 'failed',
- 'original_status': item['status'],
- 'timeleft': str(timedelta(seconds = 0)),
- })
- else:
- statuses.append({
- 'id': item['hashString'],
- 'name': item['name'],
- 'status': 'busy',
- 'original_status': item['status'],
- 'timeleft': str(timedelta(seconds = item['eta'])), # Is ETA in seconds??
- })
+ status = 'busy'
+ if item['isStalled'] and self.conf('stalled_as_failed'):
+ status = 'failed'
+ elif item['status'] == 0 and item['percentDone'] == 1:
+ status = 'completed'
+ elif item['status'] in [5, 6]:
+ status = 'seeding'
+
+ statuses.append({
+ 'id': item['hashString'],
+ 'name': item['name'],
+ 'status': status,
+ 'original_status': item['status'],
+ 'seed_ratio': item['uploadRatio'],
+ 'timeleft': str(timedelta(seconds = item['eta'])),
+ 'folder': os.path.join(item['downloadDir'], item['name']),
+ })
return statuses
+ def pause(self, item, pause = True):
+ if pause:
+ return self.trpc.stop_torrent(item['hashString'])
+ else:
+ return self.trpc.start_torrent(item['hashString'])
+
+ def removeFailed(self, item):
+ log.info('%s failed downloading, deleting...', item['name'])
+ return self.trpc.remove_torrent(self, item['hashString'], True)
+
+ def processComplete(self, item, delete_files = False):
+ log.debug('Requesting Transmission to remove the torrent %s%s.', (item['name'], ' and cleanup the downloaded files' if delete_files else ''))
+ return self.trpc.remove_torrent(self, item['hashString'], delete_files)
+
class TransmissionRPC(object):
"""TransmissionRPC lite library"""
-
def __init__(self, host = 'localhost', port = 9091, username = None, password = None):
super(TransmissionRPC, self).__init__()
@@ -184,7 +169,7 @@ class TransmissionRPC(object):
log.debug('request: %s', json.dumps(ojson))
log.debug('response: %s', json.dumps(response))
if response['result'] == 'success':
- log.debug('Transmission action successfull')
+ log.debug('Transmission action successful')
return response['arguments']
else:
log.debug('Unknown failure sending command to Transmission. Return text is: %s', response['result'])
@@ -236,13 +221,15 @@ class TransmissionRPC(object):
post_data = {'arguments': arguments, 'method': 'torrent-get', 'tag': self.tag}
return self._request(post_data)
- def stop_torrent(self, torrent_id, arguments):
- arguments['ids'] = torrent_id
- post_data = {'arguments': arguments, 'method': 'torrent-stop', 'tag': self.tag}
+ def stop_torrent(self, torrent_id):
+ post_data = {'arguments': {'ids': torrent_id}, 'method': 'torrent-stop', 'tag': self.tag}
return self._request(post_data)
- def remove_torrent(self, torrent_id, remove_local_data, arguments):
- arguments['ids'] = torrent_id
- arguments['delete-local-data'] = remove_local_data
- post_data = {'arguments': arguments, 'method': 'torrent-remove', 'tag': self.tag}
+ def start_torrent(self, torrent_id):
+ post_data = {'arguments': {'ids': torrent_id}, 'method': 'torrent-start', 'tag': self.tag}
return self._request(post_data)
+
+ def remove_torrent(self, torrent_id, delete_local_data):
+ post_data = {'arguments': {'ids': torrent_id, 'delete-local-data': delete_local_data}, 'method': 'torrent-remove', 'tag': self.tag}
+ return self._request(post_data)
+
diff --git a/couchpotato/core/downloaders/utorrent/__init__.py b/couchpotato/core/downloaders/utorrent/__init__.py
index 2c494eb..f2fcc13 100644
--- a/couchpotato/core/downloaders/utorrent/__init__.py
+++ b/couchpotato/core/downloaders/utorrent/__init__.py
@@ -37,6 +37,28 @@ config = [{
'description': 'Label to add torrent as.',
},
{
+ 'name': 'seeding',
+ 'label': 'Seeding support',
+ 'default': True,
+ 'type': 'bool',
+ 'description': '(Hard)links/copies after download is complete (if enabled in renamer), wait for seeding to finish before (re)moving.',
+ },
+ {
+ 'name': 'remove_complete',
+ 'label': 'Remove torrent',
+ 'default': True,
+ 'type': 'bool',
+ 'description': 'Remove the torrent from uTorrent after it finished seeding.',
+ },
+ {
+ 'name': 'delete_files',
+ 'label': 'Remove files',
+ 'default': True,
+ 'type': 'bool',
+ 'advanced': True,
+ 'description': 'Also remove the leftover files.',
+ },
+ {
'name': 'paused',
'type': 'bool',
'default': False,
diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py
index f49859a..738fbad 100644
--- a/couchpotato/core/downloaders/utorrent/main.py
+++ b/couchpotato/core/downloaders/utorrent/main.py
@@ -2,6 +2,7 @@ from base64 import b16encode, b32decode
from bencode import bencode, bdecode
from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import isInt, ss
+from couchpotato.core.helpers.variable import tryInt, tryFloat
from couchpotato.core.logger import CPLog
from datetime import timedelta
from hashlib import sha1
@@ -23,16 +24,40 @@ class uTorrent(Downloader):
type = ['torrent', 'torrent_magnet']
utorrent_api = None
- def download(self, data, movie, filedata = None):
-
- log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type')))
-
+ def connect(self):
# Load host from config and split out port.
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
+ if not self.utorrent_api:
+ self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
+ return self.utorrent_api
+
+ def download(self, data, movie, filedata = None):
+
+ log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type')))
+
+ if not self.connect():
+ return False
+
+ settings = self.utorrent_api.get_settings()
+ if not settings:
+ return False
+
+ #Fix settings in case they are not set for CPS compatibility
+ new_settings = {}
+ if self.conf('seeding') and not (settings.get('seed_prio_limitul') == 0 and settings['seed_prio_limitul_flag']):
+ new_settings['seed_prio_limitul'] = 0
+ new_settings['seed_prio_limitul_flag'] = True
+ log.info('Updated uTorrent settings to set a torrent to complete after it the seeding requirements are met.')
+ if settings.get('bt.read_only_on_complete'): #This doesnt work as this option seems to be not available through the api
+ new_settings['bt.read_only_on_complete'] = False
+ log.info('Updated uTorrent settings to not set the files to read only after completing.')
+ if new_settings:
+ self.utorrent_api.set_settings(new_settings)
+
torrent_params = {}
if self.conf('label'):
torrent_params['label'] = self.conf('label')
@@ -49,75 +74,72 @@ class uTorrent(Downloader):
torrent_hash = sha1(bencode(info)).hexdigest().upper()
torrent_filename = self.createFileName(data, filedata, movie)
+ if data.get('seed_ratio') and self.conf('seeding'):
+ torrent_params['seed_override'] = 1
+ torrent_params['seed_ratio'] = tryInt(tryFloat(data['seed_ratio'])*1000)
+
+ if data.get('seed_time') and self.conf('seeding'):
+ torrent_params['seed_override'] = 1
+ torrent_params['seed_time'] = tryInt(data['seed_time'])*3600
+
# Convert base 32 to hex
if len(torrent_hash) == 32:
torrent_hash = b16encode(b32decode(torrent_hash))
# Send request to uTorrent
- try:
- if not self.utorrent_api:
- self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
+ if data.get('type') == 'torrent_magnet':
+ self.utorrent_api.add_torrent_uri(data.get('url'))
+ else:
+ self.utorrent_api.add_torrent_file(torrent_filename, filedata)
- if data.get('type') == 'torrent_magnet':
- self.utorrent_api.add_torrent_uri(data.get('url'))
- else:
- self.utorrent_api.add_torrent_file(torrent_filename, filedata)
+ # Change settings of added torrents
+ self.utorrent_api.set_torrent(torrent_hash, torrent_params)
+ if self.conf('paused', default = 0):
+ self.utorrent_api.pause_torrent(torrent_hash)
- # Change settings of added torrents
- self.utorrent_api.set_torrent(torrent_hash, torrent_params)
- if self.conf('paused', default = 0):
- self.utorrent_api.pause_torrent(torrent_hash)
- return self.downloadReturnId(torrent_hash)
- except Exception, err:
- log.error('Failed to send torrent to uTorrent: %s', err)
- return False
+ return self.downloadReturnId(torrent_hash)
def getAllDownloadStatus(self):
log.debug('Checking uTorrent download status.')
- # Load host from config and split out port.
- host = self.conf('host').split(':')
- if not isInt(host[1]):
- log.error('Config properties are not filled in correctly, port is missing.')
+ if not self.connect():
return False
- try:
- self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
- except Exception, err:
- log.error('Failed to get uTorrent object: %s', err)
- return False
+ statuses = StatusList(self)
- data = ''
- try:
- data = self.utorrent_api.get_status()
- queue = json.loads(data)
- if queue.get('error'):
- log.error('Error getting data from uTorrent: %s', queue.get('error'))
- return False
+ data = self.utorrent_api.get_status()
+ if not data:
+ log.error('Error getting data from uTorrent')
+ return False
- except Exception, err:
- log.error('Failed to get status from uTorrent: %s', err)
+ queue = json.loads(data)
+ if queue.get('error'):
+ log.error('Error getting data from uTorrent: %s', queue.get('error'))
return False
- if queue.get('torrents', []) == []:
+ if not queue.get('torrents'):
log.debug('Nothing in queue')
return False
- statuses = StatusList(self)
-
# Get torrents
- for item in queue.get('torrents', []):
+ for item in queue['torrents']:
# item[21] = Paused | Downloading | Seeding | Finished
status = 'busy'
- if item[21] == 'Finished' or item[21] == 'Seeding':
+ if 'Finished' in item[21]:
status = 'completed'
+ elif 'Seeding' in item[21]:
+ if self.conf('seeding'):
+ status = 'seeding'
+ else:
+ status = 'completed'
statuses.append({
'id': item[0],
'name': item[2],
'status': status,
+ 'seed_ratio': float(item[7])/1000,
'original_status': item[1],
'timeleft': str(timedelta(seconds = item[10])),
'folder': item[26],
@@ -125,7 +147,16 @@ class uTorrent(Downloader):
return statuses
+ def pause(self, download_info, pause = True):
+ if not self.connect():
+ return False
+ return self.utorrent_api.pause_torrent(download_info['id'], pause)
+ def processComplete(self, item, delete_files = False):
+ log.debug('Requesting uTorrent to remove the torrent %s%s.', (item['name'], ' and cleanup the downloaded files' if delete_files else ''))
+ if not self.connect():
+ return False
+ return self.utorrent_api.remove_torrent(item['id'], remove_data = delete_files)
class uTorrentAPI(object):
@@ -190,10 +221,24 @@ class uTorrentAPI(object):
action += "&s=%s&v=%s" % (k, v)
return self._request(action)
- def pause_torrent(self, hash):
- action = "action=pause&hash=%s" % hash
+ def pause_torrent(self, hash, pause = True):
+ if pause:
+ action = "action=pause&hash=%s" % hash
+ else:
+ action = "action=unpause&hash=%s" % hash
return self._request(action)
+ def stop_torrent(self, hash):
+ action = "action=stop&hash=%s" % hash
+ return self._request(action)
+
+ def remove_torrent(self, hash, remove_data = False):
+ if remove_data:
+ action = "action=removedata&hash=%s" % hash
+ else:
+ action = "action=remove&hash=%s" % hash
+ return self._request(action)
+
def get_status(self):
action = "list=1"
return self._request(action)
@@ -219,3 +264,11 @@ class uTorrentAPI(object):
log.error('Failed to get settings from uTorrent: %s', err)
return settings_dict
+
+ def set_settings(self, settings_dict = {}):
+ for key in settings_dict:
+ if isinstance(settings_dict[key], bool):
+ settings_dict[key] = 1 if settings_dict[key] else 0
+
+ action = 'action=setsetting' + ''.join(['&s=%s&v=%s' % (key, value) for (key, value) in settings_dict.items()])
+ return self._request(action)
diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css
index 60ab96b..adc4ebf 100644
--- a/couchpotato/core/plugins/movie/static/movie.css
+++ b/couchpotato/core/plugins/movie/static/movie.css
@@ -425,7 +425,9 @@
}
.movies .data .quality .available { background-color: #578bc3; }
- .movies .data .quality .snatched { background-color: #369545; }
+ .movies .data .quality .failed { background-color: #a43d34; }
+ .movies .data .quality .snatched { background-color: #a2a232; }
+ .movies .data .quality .seeding { background-color: #0a6819; }
.movies .data .quality .done {
background-color: #369545;
opacity: 1;
diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py
index 155a939..e2bb4b9 100644
--- a/couchpotato/core/plugins/renamer/__init__.py
+++ b/couchpotato/core/plugins/renamer/__init__.py
@@ -121,7 +121,7 @@ config = [{
'label': 'Torrent File Action',
'default': 'move',
'type': 'dropdown',
- 'values': [('Move', 'move'), ('Copy', 'copy'), ('Hard link', 'hardlink'), ('Sym link', 'symlink'), ('Move & Sym link', 'move_symlink')],
+ 'values': [('Move', 'move'), ('Copy', 'copy'), ('Hard link', 'hardlink'), ('Move & Sym link', 'move_symlink')],
'description': 'Define which kind of file operation you want to use for torrents. Before you start using hard links or sym links, PLEASE read about their possible drawbacks.',
'advanced': True,
},
diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py
index a2fb5dc..1070e4d 100644
--- a/couchpotato/core/plugins/renamer/main.py
+++ b/couchpotato/core/plugins/renamer/main.py
@@ -10,6 +10,7 @@ from couchpotato.core.settings.model import Library, File, Profile, Release, \
ReleaseInfo
from couchpotato.environment import Env
import errno
+import fnmatch
import os
import re
import shutil
@@ -38,7 +39,6 @@ class Renamer(Plugin):
addEvent('renamer.check_snatched', self.checkSnatched)
addEvent('app.load', self.scan)
- addEvent('app.load', self.checkSnatched)
addEvent('app.load', self.setCrons)
# Enable / disable interval
@@ -65,18 +65,19 @@ class Renamer(Plugin):
downloader = kwargs.get('downloader', None)
download_id = kwargs.get('download_id', None)
+ download_info = {'folder': movie_folder} if movie_folder else None
+ if download_info:
+ download_info.update({'id': download_id, 'downloader': downloader} if download_id else {})
+
fire_handle = fireEvent if not async else fireEventAsync
- fire_handle('renamer.scan',
- movie_folder = movie_folder,
- download_info = {'id': download_id, 'downloader': downloader} if download_id else None
- )
+ fire_handle('renamer.scan', download_info)
return {
'success': True
}
- def scan(self, movie_folder = None, download_info = None):
+ def scan(self, download_info = None):
if self.isDisabled():
return
@@ -85,6 +86,8 @@ class Renamer(Plugin):
log.info('Renamer is already running, if you see this often, check the logs above for errors.')
return
+ movie_folder = download_info and download_info.get('folder')
+
# Check to see if the "to" folder is inside the "from" folder.
if movie_folder and not os.path.isdir(movie_folder) or not os.path.isdir(self.conf('from')) or not os.path.isdir(self.conf('to')):
l = log.debug if movie_folder else log.error
@@ -97,6 +100,10 @@ class Renamer(Plugin):
log.error('The "to" and "from" folders can\'t be inside of or the same as the provided movie folder.')
return
+ # Make sure a checkSnatched marked all downloads/seeds as such
+ if not download_info and self.conf('run_every') > 0:
+ fireEvent('renamer.check_snatched')
+
self.renaming_started = True
# make sure the movie folder name is included in the search
@@ -144,7 +151,7 @@ class Renamer(Plugin):
# Add _UNKNOWN_ if no library item is connected
if not group['library'] or not movie_title:
- self.tagDir(group, 'unknown')
+ self.tagDir(group['parentdir'], 'unknown')
continue
# Rename the files using the library data
else:
@@ -192,7 +199,7 @@ class Renamer(Plugin):
# Move nfo depending on settings
if file_type is 'nfo' and not self.conf('rename_nfo'):
log.debug('Skipping, renaming of %s disabled', file_type)
- if self.conf('cleanup'):
+ if self.conf('cleanup') and not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)):
for current_file in group['files'][file_type]:
remove_files.append(current_file)
continue
@@ -354,7 +361,7 @@ class Renamer(Plugin):
log.info('Better quality release already exists for %s, with quality %s', (movie.library.titles[0].title, release.quality.label))
# Add exists tag to the .ignore file
- self.tagDir(group, 'exists')
+ self.tagDir(group['parentdir'], 'exists')
# Notify on rename fail
download_message = 'Renaming of %s (%s) canceled, exists in %s already.' % (movie.library.titles[0].title, group['meta_data']['quality']['label'], release.quality.label)
@@ -405,7 +412,7 @@ class Renamer(Plugin):
except:
log.error('Failed removing %s: %s', (src, traceback.format_exc()))
- self.tagDir(group, 'failed_remove')
+ self.tagDir(group['parentdir'], 'failed_remove')
# Delete leftover folder from older releases
for delete_folder in delete_folders:
@@ -425,14 +432,16 @@ class Renamer(Plugin):
self.makeDir(os.path.dirname(dst))
try:
- self.moveFile(src, dst, forcemove = not self.downloadIsTorrent(download_info))
+ self.moveFile(src, dst, forcemove = not self.downloadIsTorrent(download_info) or self.fileIsAdded(src, group))
group['renamed_files'].append(dst)
except:
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
- self.tagDir(group, 'failed_rename')
+ self.tagDir(group['parentdir'], 'failed_rename')
- if self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info):
- self.tagDir(group, 'renamed already')
+ # Tag folder if it is in the 'from' folder and it will not be removed because it is a torrent
+ if (movie_folder and self.conf('from') in movie_folder or not movie_folder) and \
+ self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info):
+ self.tagDir(group['parentdir'], 'renamed_already')
# Remove matching releases
for release in remove_releases:
@@ -480,12 +489,9 @@ class Renamer(Plugin):
return rename_files
# This adds a file to ignore / tag a release so it is ignored later
- def tagDir(self, group, tag):
-
- ignore_file = None
- for movie_file in sorted(list(group['files']['movie'])):
- ignore_file = '%s.ignore' % os.path.splitext(movie_file)[0]
- break
+ def tagDir(self, folder, tag):
+ if not os.path.isdir(folder) or not tag:
+ return
text = """This file is from CouchPotato
It has marked this release as "%s"
@@ -493,9 +499,27 @@ This file hides the release from the renamer
Remove it if you want it to be renamed (again, or at least let it try again)
""" % tag
- if ignore_file:
- self.createFile(ignore_file, text)
+ self.createFile(os.path.join(folder, '%s.ignore' % tag), text)
+
+ def untagDir(self, folder, tag = None):
+ if not os.path.isdir(folder):
+ return
+
+ # Remove any .ignore files
+ for root, dirnames, filenames in os.walk(folder):
+ for filename in fnmatch.filter(filenames, '%s.ignore' % tag if tag else '*'):
+ os.remove((os.path.join(root, filename)))
+
+ def hastagDir(self, folder, tag = None):
+ if not os.path.isdir(folder):
+ return False
+ # Find any .ignore files
+ for root, dirnames, filenames in os.walk(folder):
+ if fnmatch.filter(filenames, '%s.ignore' % tag if tag else '*'):
+ return True
+
+ return False
def moveFile(self, old, dest, forcemove = False):
dest = ss(dest)
@@ -504,8 +528,6 @@ Remove it if you want it to be renamed (again, or at least let it try again)
shutil.move(old, dest)
elif self.conf('file_action') == 'hardlink':
link(old, dest)
- elif self.conf('file_action') == 'symlink':
- symlink(old, dest)
elif self.conf('file_action') == 'copy':
shutil.copy(old, dest)
elif self.conf('file_action') == 'move_symlink':
@@ -584,19 +606,21 @@ Remove it if you want it to be renamed (again, or at least let it try again)
if self.checking_snatched:
log.debug('Already checking snatched')
+ return False
self.checking_snatched = True
- snatched_status, ignored_status, failed_status, done_status = \
- fireEvent('status.get', ['snatched', 'ignored', 'failed', 'done'], single = True)
+ snatched_status, ignored_status, failed_status, done_status, seeding_status, downloaded_status = \
+ fireEvent('status.get', ['snatched', 'ignored', 'failed', 'done', 'seeding', 'downloaded'], single = True)
db = get_session()
rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all()
+ rels.extend(db.query(Release).filter_by(status_id = seeding_status.get('id')).all())
+ scan_items = []
scan_required = False
if rels:
- self.checking_snatched = True
log.debug('Checking status snatched releases...')
statuses = fireEvent('download.status', merge = True)
@@ -612,7 +636,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
default_title = getTitle(rel.movie.library)
# Check if movie has already completed and is manage tab (legacy db correction)
- if rel.movie.status_id == done_status.get('id'):
+ if rel.movie.status_id == done_status.get('id') and rel.status_id == snatched_status.get('id'):
log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title)
rel.status_id = ignored_status.get('id')
rel.last_edit = int(time.time())
@@ -640,7 +664,35 @@ Remove it if you want it to be renamed (again, or at least let it try again)
log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft))
if item['status'] == 'busy':
+ # Tag folder if it is in the 'from' folder and it will not be processed because it is still downloading
+ if item['folder'] and self.conf('from') in item['folder']:
+ self.tagDir(item['folder'], 'downloading')
+
pass
+ elif item['status'] == 'seeding':
+ #If linking setting is enabled, process release
+ if self.conf('file_action') != 'move' and not rel.movie.status_id == done_status.get('id') and item['id'] and item['downloader'] and item['folder']:
+ log.info('Download of %s completed! It is now being processed while leaving the original files alone for seeding. Current ratio: %s.', (item['name'], item['seed_ratio']))
+
+ # Remove the downloading tag
+ self.untagDir(item['folder'], 'downloading')
+
+ rel.status_id = seeding_status.get('id')
+ rel.last_edit = int(time.time())
+ db.commit()
+
+ # Scan and set the torrent to paused if required
+ item.update({'pause': True, 'scan': True, 'process_complete': False})
+ scan_items.append(item)
+ else:
+ if rel.status_id != seeding_status.get('id'):
+ rel.status_id = seeding_status.get('id')
+ rel.last_edit = int(time.time())
+ db.commit()
+
+ #let it seed
+ log.debug('%s is seeding with ratio: %s', (item['name'], item['seed_ratio']))
+ pass
elif item['status'] == 'failed':
fireEvent('download.remove_failed', item, single = True)
rel.status_id = failed_status.get('id')
@@ -652,7 +704,35 @@ Remove it if you want it to be renamed (again, or at least let it try again)
elif item['status'] == 'completed':
log.info('Download of %s completed!', item['name'])
if item['id'] and item['downloader'] and item['folder']:
- fireEventAsync('renamer.scan', movie_folder = item['folder'], download_info = item)
+
+ # If the release has been seeding, process now the seeding is done
+ if rel.status_id == seeding_status.get('id'):
+ if rel.movie.status_id == done_status.get('id'): # and self.conf('file_action') != 'move':
+ # Set the release to done as the movie has already been renamed
+ rel.status_id = downloaded_status.get('id')
+ rel.last_edit = int(time.time())
+ db.commit()
+
+ # Allow the downloader to clean-up
+ item.update({'pause': False, 'scan': False, 'process_complete': True})
+ scan_items.append(item)
+ else:
+ # Set the release to snatched so that the renamer can process the release as if it was never seeding
+ rel.status_id = snatched_status.get('id')
+ rel.last_edit = int(time.time())
+ db.commit()
+
+ # Scan and Allow the downloader to clean-up
+ item.update({'pause': False, 'scan': True, 'process_complete': True})
+ scan_items.append(item)
+
+ else:
+ # Remove the downloading tag
+ self.untagDir(item['folder'], 'downloading')
+
+ # Scan and Allow the downloader to clean-up
+ item.update({'pause': False, 'scan': True, 'process_complete': True})
+ scan_items.append(item)
else:
scan_required = True
@@ -665,6 +745,23 @@ Remove it if you want it to be renamed (again, or at least let it try again)
except:
log.error('Failed checking for release in downloader: %s', traceback.format_exc())
+ # The following can either be done here, or inside the scanner if we pass it scan_items in one go
+ for item in scan_items:
+ # Ask the renamer to scan the item
+ if item['scan']:
+ if item['pause'] and self.conf('file_action') == 'move_symlink':
+ fireEvent('download.pause', item = item, pause = True, single = True)
+ fireEvent('renamer.scan', download_info = item)
+ if item['pause'] and self.conf('file_action') == 'move_symlink':
+ fireEvent('download.pause', item = item, pause = False, single = True)
+ if item['process_complete']:
+ #First make sure the files were succesfully processed
+ if not self.hastagDir(item['folder'], 'failed_rename'):
+ # Remove the seeding tag if it exists
+ self.untagDir(item['folder'], 'renamed_already')
+ # Ask the downloader to process the item
+ fireEvent('download.process_complete', item = item, single = True)
+
if scan_required:
fireEvent('renamer.scan')
@@ -706,3 +803,9 @@ Remove it if you want it to be renamed (again, or at least let it try again)
def downloadIsTorrent(self, download_info):
return download_info and download_info.get('type') in ['torrent', 'torrent_magnet']
+
+ def fileIsAdded(self, src, group):
+ if not group['files'].get('added'):
+ return False
+ return src in group['files']['added']
+
diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py
index a400b15..e48d274 100644
--- a/couchpotato/core/plugins/scanner/main.py
+++ b/couchpotato/core/plugins/scanner/main.py
@@ -225,6 +225,10 @@ class Scanner(Plugin):
# Remove the found files from the leftover stack
leftovers = leftovers - set(found_files)
+ exts = [getExt(ff) for ff in found_files]
+ if 'ignore' in exts:
+ ignored_identifiers.append(identifier)
+
# Break if CP wants to shut down
if self.shuttingDown():
break
@@ -251,6 +255,10 @@ class Scanner(Plugin):
# Remove the found files from the leftover stack
leftovers = leftovers - set([ff])
+ ext = getExt(ff)
+ if ext == 'ignore':
+ ignored_identifiers.append(new_identifier)
+
# Break if CP wants to shut down
if self.shuttingDown():
break
diff --git a/couchpotato/core/plugins/status/main.py b/couchpotato/core/plugins/status/main.py
index c8c8f66..8db2bf7 100644
--- a/couchpotato/core/plugins/status/main.py
+++ b/couchpotato/core/plugins/status/main.py
@@ -23,6 +23,7 @@ class StatusPlugin(Plugin):
'ignored': 'Ignored',
'available': 'Available',
'suggest': 'Suggest',
+ 'seeding': 'Seeding',
}
status_cached = {}
diff --git a/couchpotato/core/plugins/subtitle/main.py b/couchpotato/core/plugins/subtitle/main.py
index c6bef6a..0ea1de3 100644
--- a/couchpotato/core/plugins/subtitle/main.py
+++ b/couchpotato/core/plugins/subtitle/main.py
@@ -60,6 +60,7 @@ class Subtitle(Plugin):
for d_sub in downloaded:
log.info('Found subtitle (%s): %s', (d_sub.language.alpha2, files))
group['files']['subtitle'].append(d_sub.path)
+ group['files']['added'].append(d_sub.path)
group['subtitle_language'][d_sub.path] = [d_sub.language.alpha2]
return True
diff --git a/couchpotato/core/providers/base.py b/couchpotato/core/providers/base.py
index 182a031..cb7b16d 100644
--- a/couchpotato/core/providers/base.py
+++ b/couchpotato/core/providers/base.py
@@ -276,6 +276,8 @@ class ResultList(list):
'type': self.provider.type,
'provider': self.provider.getName(),
'download': self.provider.loginDownload if self.provider.urls.get('login') else self.provider.download,
+ 'seed_ratio': Env.setting('seed_ratio', section = self.provider.getName().lower(), default = ''),
+ 'seed_time': Env.setting('seed_time', section = self.provider.getName().lower(), default = ''),
'url': '',
'name': '',
'age': 0,
diff --git a/couchpotato/core/providers/torrent/awesomehd/__init__.py b/couchpotato/core/providers/torrent/awesomehd/__init__.py
index 5c8c979..e2587a5 100644
--- a/couchpotato/core/providers/torrent/awesomehd/__init__.py
+++ b/couchpotato/core/providers/torrent/awesomehd/__init__.py
@@ -24,6 +24,20 @@ config = [{
'default': '',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'only_internal',
'advanced': True,
'type': 'bool',
diff --git a/couchpotato/core/providers/torrent/hdbits/__init__.py b/couchpotato/core/providers/torrent/hdbits/__init__.py
index 8a9fc80..0b370e1 100644
--- a/couchpotato/core/providers/torrent/hdbits/__init__.py
+++ b/couchpotato/core/providers/torrent/hdbits/__init__.py
@@ -32,6 +32,20 @@ config = [{
'default': '',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/iptorrents/__init__.py b/couchpotato/core/providers/torrent/iptorrents/__init__.py
index 24f9772..c1eea5c 100644
--- a/couchpotato/core/providers/torrent/iptorrents/__init__.py
+++ b/couchpotato/core/providers/torrent/iptorrents/__init__.py
@@ -35,6 +35,20 @@ config = [{
'description': 'Only search for [FreeLeech] torrents.',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py
index 999dbb1..90f9eea 100644
--- a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py
+++ b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py
@@ -20,6 +20,20 @@ config = [{
'default': True,
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/passthepopcorn/__init__.py b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py
index a535034..f0cb966 100644
--- a/couchpotato/core/providers/torrent/passthepopcorn/__init__.py
+++ b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py
@@ -63,6 +63,20 @@ config = [{
'description': 'Require staff-approval for releases to be accepted.'
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
@@ -71,6 +85,6 @@ config = [{
'description': 'Starting score for each release found via this provider.',
}
],
-}
+ }
]
}]
diff --git a/couchpotato/core/providers/torrent/publichd/__init__.py b/couchpotato/core/providers/torrent/publichd/__init__.py
index 3c27cf4..22e2dbb 100644
--- a/couchpotato/core/providers/torrent/publichd/__init__.py
+++ b/couchpotato/core/providers/torrent/publichd/__init__.py
@@ -20,6 +20,20 @@ config = [{
'default': True,
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/sceneaccess/__init__.py b/couchpotato/core/providers/torrent/sceneaccess/__init__.py
index baad57f..786f28a 100644
--- a/couchpotato/core/providers/torrent/sceneaccess/__init__.py
+++ b/couchpotato/core/providers/torrent/sceneaccess/__init__.py
@@ -29,6 +29,20 @@ config = [{
'type': 'password',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/scenehd/__init__.py b/couchpotato/core/providers/torrent/scenehd/__init__.py
index 3cd2132..9b967f4 100644
--- a/couchpotato/core/providers/torrent/scenehd/__init__.py
+++ b/couchpotato/core/providers/torrent/scenehd/__init__.py
@@ -29,6 +29,20 @@ config = [{
'type': 'password',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/thepiratebay/__init__.py b/couchpotato/core/providers/torrent/thepiratebay/__init__.py
index f2394dd..2c90243 100644
--- a/couchpotato/core/providers/torrent/thepiratebay/__init__.py
+++ b/couchpotato/core/providers/torrent/thepiratebay/__init__.py
@@ -26,6 +26,20 @@ config = [{
'description': 'Domain for requests, keep empty to let CouchPotato pick.',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/torrentbytes/__init__.py b/couchpotato/core/providers/torrent/torrentbytes/__init__.py
index 10e581a..c7f4437 100644
--- a/couchpotato/core/providers/torrent/torrentbytes/__init__.py
+++ b/couchpotato/core/providers/torrent/torrentbytes/__init__.py
@@ -29,6 +29,20 @@ config = [{
'type': 'password',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/torrentday/__init__.py b/couchpotato/core/providers/torrent/torrentday/__init__.py
index de715b5..d3bbaa1 100644
--- a/couchpotato/core/providers/torrent/torrentday/__init__.py
+++ b/couchpotato/core/providers/torrent/torrentday/__init__.py
@@ -29,6 +29,20 @@ config = [{
'type': 'password',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/torrentleech/__init__.py b/couchpotato/core/providers/torrent/torrentleech/__init__.py
index fa048d5..d3ee761 100644
--- a/couchpotato/core/providers/torrent/torrentleech/__init__.py
+++ b/couchpotato/core/providers/torrent/torrentleech/__init__.py
@@ -29,6 +29,20 @@ config = [{
'type': 'password',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/core/providers/torrent/torrentshack/__init__.py b/couchpotato/core/providers/torrent/torrentshack/__init__.py
index 203e099..69ad176 100644
--- a/couchpotato/core/providers/torrent/torrentshack/__init__.py
+++ b/couchpotato/core/providers/torrent/torrentshack/__init__.py
@@ -28,6 +28,20 @@ config = [{
'type': 'password',
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'scene_only',
'type': 'bool',
'default': False,
diff --git a/couchpotato/core/providers/torrent/yify/__init__.py b/couchpotato/core/providers/torrent/yify/__init__.py
index 70d6568..f953e80 100644
--- a/couchpotato/core/providers/torrent/yify/__init__.py
+++ b/couchpotato/core/providers/torrent/yify/__init__.py
@@ -20,6 +20,20 @@ config = [{
'default': 0
},
{
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Torrent will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Torrent will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js
index 8e6cced..db90c30 100644
--- a/couchpotato/static/scripts/couchpotato.js
+++ b/couchpotato/static/scripts/couchpotato.js
@@ -1,4 +1,4 @@
-var CouchPotato = new Class({
+var CouchPotato = new Class({
Implements: [Events, Options],
@@ -179,7 +179,7 @@ var CouchPotato = new Class({
shutdown: function(){
var self = this;
- self.blockPage('You have shutdown. This is what suppose to happen ;)');
+ self.blockPage('You have shutdown. This is what is supposed to happen ;)');
Api.request('app.shutdown', {
'onComplete': self.blockPage.bind(self)
});