Browse Source

Merge branch 'develop_seed' of git://github.com/mano3m/CouchPotatoServer into mano3m-develop_seed

pull/1921/merge
Ruud 12 years ago
parent
commit
d3d3106fc9
  1. 2
      couchpotato/core/_base/_core/main.py
  2. 31
      couchpotato/core/downloaders/base.py
  3. 7
      couchpotato/core/downloaders/sabnzbd/__init__.py
  4. 16
      couchpotato/core/downloaders/sabnzbd/main.py
  5. 44
      couchpotato/core/downloaders/transmission/__init__.py
  6. 171
      couchpotato/core/downloaders/transmission/main.py
  7. 22
      couchpotato/core/downloaders/utorrent/__init__.py
  8. 117
      couchpotato/core/downloaders/utorrent/main.py
  9. 4
      couchpotato/core/plugins/movie/static/movie.css
  10. 2
      couchpotato/core/plugins/renamer/__init__.py
  11. 161
      couchpotato/core/plugins/renamer/main.py
  12. 8
      couchpotato/core/plugins/scanner/main.py
  13. 1
      couchpotato/core/plugins/status/main.py
  14. 1
      couchpotato/core/plugins/subtitle/main.py
  15. 2
      couchpotato/core/providers/base.py
  16. 14
      couchpotato/core/providers/torrent/awesomehd/__init__.py
  17. 14
      couchpotato/core/providers/torrent/hdbits/__init__.py
  18. 14
      couchpotato/core/providers/torrent/iptorrents/__init__.py
  19. 14
      couchpotato/core/providers/torrent/kickasstorrents/__init__.py
  20. 16
      couchpotato/core/providers/torrent/passthepopcorn/__init__.py
  21. 14
      couchpotato/core/providers/torrent/publichd/__init__.py
  22. 14
      couchpotato/core/providers/torrent/sceneaccess/__init__.py
  23. 14
      couchpotato/core/providers/torrent/scenehd/__init__.py
  24. 14
      couchpotato/core/providers/torrent/thepiratebay/__init__.py
  25. 14
      couchpotato/core/providers/torrent/torrentbytes/__init__.py
  26. 14
      couchpotato/core/providers/torrent/torrentday/__init__.py
  27. 14
      couchpotato/core/providers/torrent/torrentleech/__init__.py
  28. 14
      couchpotato/core/providers/torrent/torrentshack/__init__.py
  29. 14
      couchpotato/core/providers/torrent/yify/__init__.py
  30. 4
      couchpotato/static/scripts/couchpotato.js

2
couchpotato/core/_base/_core/main.py

@ -124,7 +124,7 @@ class Core(Plugin):
time.sleep(1) time.sleep(1)
log.debug('Save to shutdown/restart') log.debug('Safe to shutdown/restart')
try: try:
IOLoop.current().stop() IOLoop.current().stop()

31
couchpotato/core/downloaders/base.py

@ -39,6 +39,8 @@ class Downloader(Provider):
addEvent('download.enabled_types', self.getEnabledDownloadType) addEvent('download.enabled_types', self.getEnabledDownloadType)
addEvent('download.status', self._getAllDownloadStatus) addEvent('download.status', self._getAllDownloadStatus)
addEvent('download.remove_failed', self._removeFailed) addEvent('download.remove_failed', self._removeFailed)
addEvent('download.pause', self._pause)
addEvent('download.process_complete', self._processComplete)
def getEnabledDownloadType(self): def getEnabledDownloadType(self):
for download_type in self.type: for download_type in self.type:
@ -65,14 +67,30 @@ class Downloader(Provider):
if self.isDisabled(manual = True, data = {}): if self.isDisabled(manual = True, data = {}):
return return
if self.conf('delete_failed', default = True): if item and item.get('downloader') == self.getName():
if self.conf('delete_failed'):
return self.removeFailed(item) return self.removeFailed(item)
return False return False
return
def removeFailed(self, item): def removeFailed(self, item):
return 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): def isCorrectType(self, item_type):
is_correct = item_type in self.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 \ ((d_manual and manual) or (d_manual is False)) and \
(not data or self.isCorrectType(data.get('type'))) (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): class StatusList(list):

7
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.', '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', 'name': 'delete_failed',
'default': True, 'default': True,
'type': 'bool', 'type': 'bool',

16
couchpotato/core/downloaders/sabnzbd/main.py

@ -129,6 +129,22 @@ class Sabnzbd(Downloader):
return True 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): def call(self, request_params, use_json = True, **kwargs):
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(request_params, { url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(request_params, {

44
couchpotato/core/downloaders/transmission/__init__.py

@ -44,18 +44,32 @@ config = [{
'description': 'Download to this directory. Keep empty for default Transmission download directory.', 'description': 'Download to this directory. Keep empty for default Transmission download directory.',
}, },
{ {
'name': 'ratio', 'name': 'seeding',
'default': 10, 'label': 'Seeding support',
'type': 'float', 'default': True,
'advanced': True, 'type': 'bool',
'description': 'Stop transfer when reaching ratio', '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': 'ratiomode', 'name': 'remove_complete',
'default': 0, 'label': 'Remove torrent',
'type': 'int', '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, 'advanced': True,
'description': '0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.', 'description': 'Also remove the leftover files.',
},
{
'name': 'paused',
'type': 'bool',
'default': False,
'description': 'Add the torrent paused.',
}, },
{ {
'name': 'manual', 'name': 'manual',
@ -64,6 +78,18 @@ config = [{
'advanced': True, 'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', '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.',
},
], ],
} }
], ],

171
couchpotato/core/downloaders/transmission/main.py

@ -1,6 +1,7 @@
from base64 import b64encode from base64 import b64encode
from couchpotato.core.downloaders.base import Downloader, StatusList from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import isInt from couchpotato.core.helpers.encoding import isInt
from couchpotato.core.helpers.variable import tryInt, tryFloat
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.environment import Env from couchpotato.environment import Env
from datetime import timedelta from datetime import timedelta
@ -8,7 +9,6 @@ import httplib
import json import json
import os.path import os.path
import re import re
import traceback
import urllib2 import urllib2
log = CPLog(__name__) log = CPLog(__name__)
@ -18,144 +18,129 @@ class Transmission(Downloader):
type = ['torrent', 'torrent_magnet'] type = ['torrent', 'torrent_magnet']
log = CPLog(__name__) log = CPLog(__name__)
trpc = None
def download(self, data, movie, filedata = None): def connect(self):
log.info('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
# Load host from config and split out port. # Load host from config and split out port.
host = self.conf('host').split(':') host = self.conf('host').split(':')
if not isInt(host[1]): if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.') log.error('Config properties are not filled in correctly, port is missing.')
return False 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 def download(self, data, movie, filedata = None):
params = {
'paused': self.conf('paused', default = 0),
}
if len(self.conf('directory', default = '')) > 0: log.info('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
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)
torrent_params = {} if not self.connect():
if self.conf('ratio'): return False
torrent_params = {
'seedRatioLimit': self.conf('ratio'),
'seedRatioMode': self.conf('ratiomode')
}
if not filedata and data.get('type') == 'torrent': if not filedata and data.get('type') == 'torrent':
log.error('Failed sending torrent, no data') log.error('Failed sending torrent, no data')
return False return False
# 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:
log.error('Download directory from Transmission settings: %s doesn\'t exist', self.conf('directory'))
# 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
if data.get('seed_time') and self.conf('seeding'):
torrent_params['seedIdleLimit'] = tryInt(data.get('seed_time'))*60
torrent_params['seedIdleMode'] = 1
# Send request to Transmission # 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': if data.get('type') == 'torrent_magnet':
remote_torrent = trpc.add_torrent_uri(data.get('url'), arguments = params) remote_torrent = self.trpc.add_torrent_uri(data.get('url'), arguments = params)
torrent_params['trackerAdd'] = self.torrent_trackers torrent_params['trackerAdd'] = self.torrent_trackers
else: else:
remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params) remote_torrent = self.trpc.add_torrent_file(b64encode(filedata), arguments = params)
if not remote_torrent: if not remote_torrent:
log.error('Failed sending torrent to Transmission')
return False return False
# Change settings of added torrents # Change settings of added torrents
elif torrent_params: if torrent_params:
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params) self.trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
log.info('Torrent sent to Transmission successfully.') log.info('Torrent sent to Transmission successfully.')
return self.downloadReturnId(remote_torrent['torrent-added']['hashString']) return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
except:
log.error('Failed to change settings for transfer: %s', traceback.format_exc())
return False
def getAllDownloadStatus(self): def getAllDownloadStatus(self):
log.debug('Checking Transmission download status.') log.debug('Checking Transmission download status.')
# Load host from config and split out port. if not self.connect():
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False return False
# Go through Queue statuses = StatusList(self)
try:
trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
return_params = { return_params = {
'fields': ['id', 'name', 'hashString', 'percentDone', 'status', 'eta', 'isFinished', 'downloadDir', 'uploadRatio'] 'fields': ['id', 'name', 'hashString', 'percentDone', 'status', 'eta', 'isStalled', 'isFinished', 'downloadDir', 'uploadRatio', 'secondsSeeding', 'seedIdleLimit']
} }
queue = trpc.get_alltorrents(return_params)
except Exception, err:
log.error('Failed getting queue: %s', err)
return False
if not queue: queue = self.trpc.get_alltorrents(return_params)
return [] if not (queue and queue.get('torrents')):
log.debug('Nothing in queue or error')
statuses = StatusList(self) return False
# 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
for item in queue['torrents']: 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')): if not os.path.isdir(Env.setting('from', 'renamer')):
log.error('Renamer "from" folder doesn\'t to exist.') log.error('Renamer "from" folder doesn\'t to exist.')
return return
if (item['percentDone'] * 100) >= 100 and (item['status'] == 6 or item['status'] == 0) and item['uploadRatio'] > self.conf('ratio'): status = 'busy'
try: if item['isStalled'] and self.conf('stalled_as_failed'):
trpc.stop_torrent(item['hashString'], {}) status = 'failed'
elif item['status'] == 0 and item['percentDone'] == 1:
status = 'completed'
elif item['status'] in [5, 6]:
status = 'seeding'
statuses.append({ statuses.append({
'id': item['hashString'], 'id': item['hashString'],
'name': item['name'], 'name': item['name'],
'status': 'completed', 'status': status,
'original_status': item['status'], 'original_status': item['status'],
'timeleft': str(timedelta(seconds = 0)), 'seed_ratio': item['uploadRatio'],
'timeleft': str(timedelta(seconds = item['eta'])),
'folder': os.path.join(item['downloadDir'], item['name']), '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??
})
return statuses 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): class TransmissionRPC(object):
"""TransmissionRPC lite library""" """TransmissionRPC lite library"""
def __init__(self, host = 'localhost', port = 9091, username = None, password = None): def __init__(self, host = 'localhost', port = 9091, username = None, password = None):
super(TransmissionRPC, self).__init__() super(TransmissionRPC, self).__init__()
@ -184,7 +169,7 @@ class TransmissionRPC(object):
log.debug('request: %s', json.dumps(ojson)) log.debug('request: %s', json.dumps(ojson))
log.debug('response: %s', json.dumps(response)) log.debug('response: %s', json.dumps(response))
if response['result'] == 'success': if response['result'] == 'success':
log.debug('Transmission action successfull') log.debug('Transmission action successful')
return response['arguments'] return response['arguments']
else: else:
log.debug('Unknown failure sending command to Transmission. Return text is: %s', response['result']) 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} post_data = {'arguments': arguments, 'method': 'torrent-get', 'tag': self.tag}
return self._request(post_data) return self._request(post_data)
def stop_torrent(self, torrent_id, arguments): def stop_torrent(self, torrent_id):
arguments['ids'] = torrent_id post_data = {'arguments': {'ids': torrent_id}, 'method': 'torrent-stop', 'tag': self.tag}
post_data = {'arguments': arguments, 'method': 'torrent-stop', 'tag': self.tag}
return self._request(post_data) return self._request(post_data)
def remove_torrent(self, torrent_id, remove_local_data, arguments): def start_torrent(self, torrent_id):
arguments['ids'] = torrent_id post_data = {'arguments': {'ids': torrent_id}, 'method': 'torrent-start', 'tag': self.tag}
arguments['delete-local-data'] = remove_local_data
post_data = {'arguments': arguments, 'method': 'torrent-remove', 'tag': self.tag}
return self._request(post_data) 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)

22
couchpotato/core/downloaders/utorrent/__init__.py

@ -37,6 +37,28 @@ config = [{
'description': 'Label to add torrent as.', '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', 'name': 'paused',
'type': 'bool', 'type': 'bool',
'default': False, 'default': False,

117
couchpotato/core/downloaders/utorrent/main.py

@ -2,6 +2,7 @@ from base64 import b16encode, b32decode
from bencode import bencode, bdecode from bencode import bencode, bdecode
from couchpotato.core.downloaders.base import Downloader, StatusList from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import isInt, ss from couchpotato.core.helpers.encoding import isInt, ss
from couchpotato.core.helpers.variable import tryInt, tryFloat
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from datetime import timedelta from datetime import timedelta
from hashlib import sha1 from hashlib import sha1
@ -23,16 +24,40 @@ class uTorrent(Downloader):
type = ['torrent', 'torrent_magnet'] type = ['torrent', 'torrent_magnet']
utorrent_api = None utorrent_api = None
def download(self, data, movie, filedata = None): def connect(self):
log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type')))
# Load host from config and split out port. # Load host from config and split out port.
host = self.conf('host').split(':') host = self.conf('host').split(':')
if not isInt(host[1]): if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.') log.error('Config properties are not filled in correctly, port is missing.')
return False 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 = {} torrent_params = {}
if self.conf('label'): if self.conf('label'):
torrent_params['label'] = self.conf('label') torrent_params['label'] = self.conf('label')
@ -49,15 +74,19 @@ class uTorrent(Downloader):
torrent_hash = sha1(bencode(info)).hexdigest().upper() torrent_hash = sha1(bencode(info)).hexdigest().upper()
torrent_filename = self.createFileName(data, filedata, movie) 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 # Convert base 32 to hex
if len(torrent_hash) == 32: if len(torrent_hash) == 32:
torrent_hash = b16encode(b32decode(torrent_hash)) torrent_hash = b16encode(b32decode(torrent_hash))
# Send request to uTorrent # 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': if data.get('type') == 'torrent_magnet':
self.utorrent_api.add_torrent_uri(data.get('url')) self.utorrent_api.add_torrent_uri(data.get('url'))
else: else:
@ -67,57 +96,50 @@ class uTorrent(Downloader):
self.utorrent_api.set_torrent(torrent_hash, torrent_params) self.utorrent_api.set_torrent(torrent_hash, torrent_params)
if self.conf('paused', default = 0): if self.conf('paused', default = 0):
self.utorrent_api.pause_torrent(torrent_hash) self.utorrent_api.pause_torrent(torrent_hash)
return self.downloadReturnId(torrent_hash) return self.downloadReturnId(torrent_hash)
except Exception, err:
log.error('Failed to send torrent to uTorrent: %s', err)
return False
def getAllDownloadStatus(self): def getAllDownloadStatus(self):
log.debug('Checking uTorrent download status.') log.debug('Checking uTorrent download status.')
# Load host from config and split out port. if not self.connect():
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False return False
try: statuses = StatusList(self)
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
data = ''
try:
data = self.utorrent_api.get_status() data = self.utorrent_api.get_status()
if not data:
log.error('Error getting data from uTorrent')
return False
queue = json.loads(data) queue = json.loads(data)
if queue.get('error'): if queue.get('error'):
log.error('Error getting data from uTorrent: %s', queue.get('error')) log.error('Error getting data from uTorrent: %s', queue.get('error'))
return False return False
except Exception, err: if not queue.get('torrents'):
log.error('Failed to get status from uTorrent: %s', err)
return False
if queue.get('torrents', []) == []:
log.debug('Nothing in queue') log.debug('Nothing in queue')
return False return False
statuses = StatusList(self)
# Get torrents # Get torrents
for item in queue.get('torrents', []): for item in queue['torrents']:
# item[21] = Paused | Downloading | Seeding | Finished # item[21] = Paused | Downloading | Seeding | Finished
status = 'busy' 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' status = 'completed'
statuses.append({ statuses.append({
'id': item[0], 'id': item[0],
'name': item[2], 'name': item[2],
'status': status, 'status': status,
'seed_ratio': float(item[7])/1000,
'original_status': item[1], 'original_status': item[1],
'timeleft': str(timedelta(seconds = item[10])), 'timeleft': str(timedelta(seconds = item[10])),
'folder': item[26], 'folder': item[26],
@ -125,7 +147,16 @@ class uTorrent(Downloader):
return statuses 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): class uTorrentAPI(object):
@ -190,8 +221,22 @@ class uTorrentAPI(object):
action += "&s=%s&v=%s" % (k, v) action += "&s=%s&v=%s" % (k, v)
return self._request(action) return self._request(action)
def pause_torrent(self, hash): def pause_torrent(self, hash, pause = True):
if pause:
action = "action=pause&hash=%s" % hash 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) return self._request(action)
def get_status(self): def get_status(self):
@ -219,3 +264,11 @@ class uTorrentAPI(object):
log.error('Failed to get settings from uTorrent: %s', err) log.error('Failed to get settings from uTorrent: %s', err)
return settings_dict 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)

4
couchpotato/core/plugins/movie/static/movie.css

@ -425,7 +425,9 @@
} }
.movies .data .quality .available { background-color: #578bc3; } .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 { .movies .data .quality .done {
background-color: #369545; background-color: #369545;
opacity: 1; opacity: 1;

2
couchpotato/core/plugins/renamer/__init__.py

@ -121,7 +121,7 @@ config = [{
'label': 'Torrent File Action', 'label': 'Torrent File Action',
'default': 'move', 'default': 'move',
'type': 'dropdown', '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 <a href="http://en.wikipedia.org/wiki/Hard_link">hard links</a> or <a href="http://en.wikipedia.org/wiki/Sym_link">sym links</a>, PLEASE read about their possible drawbacks.', 'description': 'Define which kind of file operation you want to use for torrents. Before you start using <a href="http://en.wikipedia.org/wiki/Hard_link">hard links</a> or <a href="http://en.wikipedia.org/wiki/Sym_link">sym links</a>, PLEASE read about their possible drawbacks.',
'advanced': True, 'advanced': True,
}, },

161
couchpotato/core/plugins/renamer/main.py

@ -10,6 +10,7 @@ from couchpotato.core.settings.model import Library, File, Profile, Release, \
ReleaseInfo ReleaseInfo
from couchpotato.environment import Env from couchpotato.environment import Env
import errno import errno
import fnmatch
import os import os
import re import re
import shutil import shutil
@ -38,7 +39,6 @@ class Renamer(Plugin):
addEvent('renamer.check_snatched', self.checkSnatched) addEvent('renamer.check_snatched', self.checkSnatched)
addEvent('app.load', self.scan) addEvent('app.load', self.scan)
addEvent('app.load', self.checkSnatched)
addEvent('app.load', self.setCrons) addEvent('app.load', self.setCrons)
# Enable / disable interval # Enable / disable interval
@ -65,18 +65,19 @@ class Renamer(Plugin):
downloader = kwargs.get('downloader', None) downloader = kwargs.get('downloader', None)
download_id = kwargs.get('download_id', 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 = fireEvent if not async else fireEventAsync
fire_handle('renamer.scan', fire_handle('renamer.scan', download_info)
movie_folder = movie_folder,
download_info = {'id': download_id, 'downloader': downloader} if download_id else None
)
return { return {
'success': True 'success': True
} }
def scan(self, movie_folder = None, download_info = None): def scan(self, download_info = None):
if self.isDisabled(): if self.isDisabled():
return 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.') log.info('Renamer is already running, if you see this often, check the logs above for errors.')
return return
movie_folder = download_info and download_info.get('folder')
# Check to see if the "to" folder is inside the "from" 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')): 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 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.') log.error('The "to" and "from" folders can\'t be inside of or the same as the provided movie folder.')
return 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 self.renaming_started = True
# make sure the movie folder name is included in the search # 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 # Add _UNKNOWN_ if no library item is connected
if not group['library'] or not movie_title: if not group['library'] or not movie_title:
self.tagDir(group, 'unknown') self.tagDir(group['parentdir'], 'unknown')
continue continue
# Rename the files using the library data # Rename the files using the library data
else: else:
@ -192,7 +199,7 @@ class Renamer(Plugin):
# Move nfo depending on settings # Move nfo depending on settings
if file_type is 'nfo' and not self.conf('rename_nfo'): if file_type is 'nfo' and not self.conf('rename_nfo'):
log.debug('Skipping, renaming of %s disabled', file_type) 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]: for current_file in group['files'][file_type]:
remove_files.append(current_file) remove_files.append(current_file)
continue 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)) 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 # Add exists tag to the .ignore file
self.tagDir(group, 'exists') self.tagDir(group['parentdir'], 'exists')
# Notify on rename fail # 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) 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: except:
log.error('Failed removing %s: %s', (src, traceback.format_exc())) 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 # Delete leftover folder from older releases
for delete_folder in delete_folders: for delete_folder in delete_folders:
@ -425,14 +432,16 @@ class Renamer(Plugin):
self.makeDir(os.path.dirname(dst)) self.makeDir(os.path.dirname(dst))
try: 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) group['renamed_files'].append(dst)
except: except:
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc())) 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): # Tag folder if it is in the 'from' folder and it will not be removed because it is a torrent
self.tagDir(group, 'renamed already') 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 # Remove matching releases
for release in remove_releases: for release in remove_releases:
@ -480,12 +489,9 @@ class Renamer(Plugin):
return rename_files return rename_files
# This adds a file to ignore / tag a release so it is ignored later # This adds a file to ignore / tag a release so it is ignored later
def tagDir(self, group, tag): def tagDir(self, folder, tag):
if not os.path.isdir(folder) or not tag:
ignore_file = None return
for movie_file in sorted(list(group['files']['movie'])):
ignore_file = '%s.ignore' % os.path.splitext(movie_file)[0]
break
text = """This file is from CouchPotato text = """This file is from CouchPotato
It has marked this release as "%s" 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) Remove it if you want it to be renamed (again, or at least let it try again)
""" % tag """ % tag
if ignore_file: self.createFile(os.path.join(folder, '%s.ignore' % tag), text)
self.createFile(ignore_file, 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): def moveFile(self, old, dest, forcemove = False):
dest = ss(dest) 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) shutil.move(old, dest)
elif self.conf('file_action') == 'hardlink': elif self.conf('file_action') == 'hardlink':
link(old, dest) link(old, dest)
elif self.conf('file_action') == 'symlink':
symlink(old, dest)
elif self.conf('file_action') == 'copy': elif self.conf('file_action') == 'copy':
shutil.copy(old, dest) shutil.copy(old, dest)
elif self.conf('file_action') == 'move_symlink': 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: if self.checking_snatched:
log.debug('Already checking snatched') log.debug('Already checking snatched')
return False
self.checking_snatched = True self.checking_snatched = True
snatched_status, ignored_status, failed_status, done_status = \ snatched_status, ignored_status, failed_status, done_status, seeding_status, downloaded_status = \
fireEvent('status.get', ['snatched', 'ignored', 'failed', 'done'], single = True) fireEvent('status.get', ['snatched', 'ignored', 'failed', 'done', 'seeding', 'downloaded'], single = True)
db = get_session() db = get_session()
rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all() 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 scan_required = False
if rels: if rels:
self.checking_snatched = True
log.debug('Checking status snatched releases...') log.debug('Checking status snatched releases...')
statuses = fireEvent('download.status', merge = True) 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) default_title = getTitle(rel.movie.library)
# Check if movie has already completed and is manage tab (legacy db correction) # 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) 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.status_id = ignored_status.get('id')
rel.last_edit = int(time.time()) rel.last_edit = int(time.time())
@ -640,6 +664,34 @@ 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)) log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft))
if item['status'] == 'busy': 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 pass
elif item['status'] == 'failed': elif item['status'] == 'failed':
fireEvent('download.remove_failed', item, single = True) fireEvent('download.remove_failed', item, single = True)
@ -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': elif item['status'] == 'completed':
log.info('Download of %s completed!', item['name']) log.info('Download of %s completed!', item['name'])
if item['id'] and item['downloader'] and item['folder']: 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: else:
scan_required = True 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: except:
log.error('Failed checking for release in downloader: %s', traceback.format_exc()) 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: if scan_required:
fireEvent('renamer.scan') 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): def downloadIsTorrent(self, download_info):
return download_info and download_info.get('type') in ['torrent', 'torrent_magnet'] 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']

8
couchpotato/core/plugins/scanner/main.py

@ -225,6 +225,10 @@ class Scanner(Plugin):
# Remove the found files from the leftover stack # Remove the found files from the leftover stack
leftovers = leftovers - set(found_files) 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 # Break if CP wants to shut down
if self.shuttingDown(): if self.shuttingDown():
break break
@ -251,6 +255,10 @@ class Scanner(Plugin):
# Remove the found files from the leftover stack # Remove the found files from the leftover stack
leftovers = leftovers - set([ff]) leftovers = leftovers - set([ff])
ext = getExt(ff)
if ext == 'ignore':
ignored_identifiers.append(new_identifier)
# Break if CP wants to shut down # Break if CP wants to shut down
if self.shuttingDown(): if self.shuttingDown():
break break

1
couchpotato/core/plugins/status/main.py

@ -23,6 +23,7 @@ class StatusPlugin(Plugin):
'ignored': 'Ignored', 'ignored': 'Ignored',
'available': 'Available', 'available': 'Available',
'suggest': 'Suggest', 'suggest': 'Suggest',
'seeding': 'Seeding',
} }
status_cached = {} status_cached = {}

1
couchpotato/core/plugins/subtitle/main.py

@ -60,6 +60,7 @@ class Subtitle(Plugin):
for d_sub in downloaded: for d_sub in downloaded:
log.info('Found subtitle (%s): %s', (d_sub.language.alpha2, files)) log.info('Found subtitle (%s): %s', (d_sub.language.alpha2, files))
group['files']['subtitle'].append(d_sub.path) group['files']['subtitle'].append(d_sub.path)
group['files']['added'].append(d_sub.path)
group['subtitle_language'][d_sub.path] = [d_sub.language.alpha2] group['subtitle_language'][d_sub.path] = [d_sub.language.alpha2]
return True return True

2
couchpotato/core/providers/base.py

@ -276,6 +276,8 @@ class ResultList(list):
'type': self.provider.type, 'type': self.provider.type,
'provider': self.provider.getName(), 'provider': self.provider.getName(),
'download': self.provider.loginDownload if self.provider.urls.get('login') else self.provider.download, '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': '', 'url': '',
'name': '', 'name': '',
'age': 0, 'age': 0,

14
couchpotato/core/providers/torrent/awesomehd/__init__.py

@ -24,6 +24,20 @@ config = [{
'default': '', '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', 'name': 'only_internal',
'advanced': True, 'advanced': True,
'type': 'bool', 'type': 'bool',

14
couchpotato/core/providers/torrent/hdbits/__init__.py

@ -32,6 +32,20 @@ config = [{
'default': '', '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

14
couchpotato/core/providers/torrent/iptorrents/__init__.py

@ -35,6 +35,20 @@ config = [{
'description': 'Only search for [FreeLeech] torrents.', '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

14
couchpotato/core/providers/torrent/kickasstorrents/__init__.py

@ -20,6 +20,20 @@ config = [{
'default': True, '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

16
couchpotato/core/providers/torrent/passthepopcorn/__init__.py

@ -63,6 +63,20 @@ config = [{
'description': 'Require staff-approval for releases to be accepted.' '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',
@ -71,6 +85,6 @@ config = [{
'description': 'Starting score for each release found via this provider.', 'description': 'Starting score for each release found via this provider.',
} }
], ],
} }
] ]
}] }]

14
couchpotato/core/providers/torrent/publichd/__init__.py

@ -20,6 +20,20 @@ config = [{
'default': True, '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

14
couchpotato/core/providers/torrent/sceneaccess/__init__.py

@ -29,6 +29,20 @@ config = [{
'type': 'password', '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

14
couchpotato/core/providers/torrent/scenehd/__init__.py

@ -29,6 +29,20 @@ config = [{
'type': 'password', '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

14
couchpotato/core/providers/torrent/thepiratebay/__init__.py

@ -26,6 +26,20 @@ config = [{
'description': 'Domain for requests, keep empty to let CouchPotato pick.', '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

14
couchpotato/core/providers/torrent/torrentbytes/__init__.py

@ -29,6 +29,20 @@ config = [{
'type': 'password', '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

14
couchpotato/core/providers/torrent/torrentday/__init__.py

@ -29,6 +29,20 @@ config = [{
'type': 'password', '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

14
couchpotato/core/providers/torrent/torrentleech/__init__.py

@ -29,6 +29,20 @@ config = [{
'type': 'password', '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

14
couchpotato/core/providers/torrent/torrentshack/__init__.py

@ -28,6 +28,20 @@ config = [{
'type': 'password', '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', 'name': 'scene_only',
'type': 'bool', 'type': 'bool',
'default': False, 'default': False,

14
couchpotato/core/providers/torrent/yify/__init__.py

@ -20,6 +20,20 @@ config = [{
'default': 0 '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', 'name': 'extra_score',
'advanced': True, 'advanced': True,
'label': 'Extra Score', 'label': 'Extra Score',

4
couchpotato/static/scripts/couchpotato.js

@ -1,4 +1,4 @@
var CouchPotato = new Class({ var CouchPotato = new Class({
Implements: [Events, Options], Implements: [Events, Options],
@ -179,7 +179,7 @@ var CouchPotato = new Class({
shutdown: function(){ shutdown: function(){
var self = this; 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', { Api.request('app.shutdown', {
'onComplete': self.blockPage.bind(self) 'onComplete': self.blockPage.bind(self)
}); });

Loading…
Cancel
Save