Merge branch 'refs/heads/develop'

This commit is contained in:
Ruud committed 2013-03-26 21:10:27 +01:00
commit 7f87b255f9
44 files changed
+535 -112

No files matched your search

+1 -1
View File
@@ -70,7 +70,7 @@ config = [{
'name': 'development', 'name': 'development',
'default': 0, 'default': 0,
'type': 'bool', 'type': 'bool',
'description': 'Disables some checks/downloads for faster reloading.', 'description': 'Enable this if you\'re developing, and NOT in any other case, thanks.',
}, },
{ {
'name': 'data_dir', 'name': 'data_dir',
+38
View File
@@ -1,5 +1,6 @@
from base64 import b32decode, b16encode from base64 import b32decode, b16encode
from couchpotato.core.event import addEvent from couchpotato.core.event import addEvent
from couchpotato.core.helpers.variable import mergeDicts
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.providers.base import Provider from couchpotato.core.providers.base import Provider
import random import random
@@ -103,6 +104,12 @@ class Downloader(Provider):
log.error('Failed converting magnet url to torrent: %s', (torrent_hash)) log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
return False return False
def downloadReturnId(self, download_id):
return {
'downloader': self.getName(),
'id': download_id
}
def isDisabled(self, manual, data): def isDisabled(self, manual, data):
return not self.isEnabled(manual, data) return not self.isEnabled(manual, data)
@@ -116,3 +123,34 @@ class Downloader(Provider):
return super(Downloader, self).isEnabled() and \ return super(Downloader, self).isEnabled() and \
((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')))
class StatusList(list):
provider = None
def __init__(self, provider, **kwargs):
self.provider = provider
self.kwargs = kwargs
super(StatusList, self).__init__()
def extend(self, results):
for r in results:
self.append(r)
def append(self, result):
new_result = self.fillResult(result)
super(StatusList, self).append(new_result)
def fillResult(self, result):
defaults = {
'id': 0,
'status': 'busy',
'downloader': self.provider.getName(),
}
return mergeDicts(defaults, result)
@@ -48,6 +48,12 @@ 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': 'delete_failed',
'default': True,
'type': 'bool',
'description': 'Delete a release after the download has failed.',
},
], ],
} }
], ],
+104 -2
View File
@@ -1,9 +1,11 @@
from base64 import standard_b64encode from base64 import standard_b64encode
from couchpotato.core.downloaders.base import Downloader from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import ss from couchpotato.core.helpers.encoding import ss
from couchpotato.core.helpers.variable import tryInt from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from datetime import timedelta
import re import re
import shutil
import socket import socket
import traceback import traceback
import xmlrpclib import xmlrpclib
@@ -50,7 +52,107 @@ class NZBGet(Downloader):
if xml_response: if xml_response:
log.info('NZB sent successfully to NZBGet') log.info('NZB sent successfully to NZBGet')
return True groups = rpc.listgroups()
nzb_id = [item['NZBID'] for item in groups if item['NZBFilename'] == nzb_name][0]
return self.downloadReturnId(nzb_id)
else: else:
log.error('NZBGet could not add %s to the queue.', nzb_name) log.error('NZBGet could not add %s to the queue.', nzb_name)
return False return False
def getAllDownloadStatus(self):
log.debug('Checking NZBGet download status.')
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
rpc = xmlrpclib.ServerProxy(url)
try:
if rpc.writelog('INFO', 'CouchPotato connected to check status'):
log.info('Successfully connected to NZBGet')
else:
log.info('Successfully connected to NZBGet, but unable to send a message')
except socket.error:
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
return False
except xmlrpclib.ProtocolError, e:
if e.errcode == 401:
log.error('Password is incorrect.')
else:
log.error('Protocol Error: %s', e)
return False
# Get NZBGet data
try:
status = rpc.status()
groups = rpc.listgroups()
queue = rpc.postqueue(0)
history = rpc.history()
except:
log.error('Failed getting data: %s', traceback.format_exc(1))
return False
statuses = StatusList(self)
for item in groups:
log.debug('Found %s in NZBGet download queue', item['NZBFilename'])
statuses.append({
'id': item['NZBID'],
'name': item['NZBFilename'],
'original_status': 'DOWNLOADING' if item['ActiveDownloads'] > 0 else 'QUEUED',
# Seems to have no native API function for time left. This will return the time left after NZBGet started downloading this item
'timeleft': str(timedelta(seconds = item['RemainingSizeMB'] / status['DownloadRate'] * 2 ^ 20)) if item['ActiveDownloads'] > 0 and not (status['DownloadPaused'] or status['Download2Paused']) else -1,
})
for item in queue:
log.debug('Found %s in NZBGet postprocessing queue', item['NZBFilename'])
statuses.append({
'id': item['NZBID'],
'name': item['NZBFilename'],
'original_status': item['Stage'],
'timeleft': str(timedelta(seconds = 0)) if not status['PostPaused'] else -1,
})
for item in history:
log.debug('Found %s in NZBGet history. ParStatus: %s, ScriptStatus: %s, Log: %s', (item['NZBFilename'] , item['ParStatus'], item['ScriptStatus'] , item['Log']))
statuses.append({
'id': item['NZBID'],
'name': item['NZBFilename'],
'status': 'completed' if item['ParStatus'] == 'SUCCESS' and item['ScriptStatus'] == 'SUCCESS' else 'failed',
'original_status': item['ParStatus'] + ', ' + item['ScriptStatus'],
'timeleft': str(timedelta(seconds = 0)),
})
return statuses
def removeFailed(self, item):
log.info('%s failed downloading, deleting...', item['name'])
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
rpc = xmlrpclib.ServerProxy(url)
try:
if rpc.writelog('INFO', 'CouchPotato connected to delete some history'):
log.info('Successfully connected to NZBGet')
else:
log.info('Successfully connected to NZBGet, but unable to send a message')
except socket.error:
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
return False
except xmlrpclib.ProtocolError, e:
if e.errcode == 401:
log.error('Password is incorrect.')
else:
log.error('Protocol Error: %s', e)
return False
try:
history = rpc.history()
if rpc.editqueue('HistoryDelete', 0, "", [tryInt(item['id'])]):
path = [hist['DestDir'] for hist in history if hist['NZBID'] == item['id']][0]
shutil.rmtree(path, True)
except:
log.error('Failed deleting: %s', traceback.format_exc(0))
return False
return True
@@ -1,5 +1,5 @@
from base64 import b64encode from base64 import b64encode
from couchpotato.core.downloaders.base import Downloader from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import tryUrlencode, ss from couchpotato.core.helpers.encoding import tryUrlencode, ss
from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.helpers.variable import cleanHost
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
@@ -29,7 +29,9 @@ class NZBVortex(Downloader):
nzb_filename = self.createFileName(data, filedata, movie) nzb_filename = self.createFileName(data, filedata, movie)
self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True) self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True)
return True raw_statuses = self.call('nzb')
nzb_id = [item['id'] for item in raw_statuses.get('nzbs', []) if item['name'] == nzb_filename][0]
return self.downloadReturnId(nzb_id)
except: except:
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc()) log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
return False return False
@@ -38,7 +40,7 @@ class NZBVortex(Downloader):
raw_statuses = self.call('nzb') raw_statuses = self.call('nzb')
statuses = [] statuses = StatusList(self)
for item in raw_statuses.get('nzbs', []): for item in raw_statuses.get('nzbs', []):
# Check status # Check status
+21 -23
View File
@@ -1,4 +1,4 @@
from couchpotato.core.downloaders.base import Downloader from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import tryUrlencode, ss from couchpotato.core.helpers.encoding import tryUrlencode, ss
from couchpotato.core.helpers.variable import cleanHost, mergeDicts from couchpotato.core.helpers.variable import cleanHost, mergeDicts
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
@@ -17,8 +17,7 @@ class Sabnzbd(Downloader):
log.info('Sending "%s" to SABnzbd.', data.get('name')) log.info('Sending "%s" to SABnzbd.', data.get('name'))
params = { req_params = {
'apikey': self.conf('api_key'),
'cat': self.conf('category'), 'cat': self.conf('category'),
'mode': 'addurl', 'mode': 'addurl',
'nzbname': self.createNzbName(data, movie), 'nzbname': self.createNzbName(data, movie),
@@ -31,17 +30,15 @@ class Sabnzbd(Downloader):
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb # If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
nzb_filename = self.createFileName(data, filedata, movie) nzb_filename = self.createFileName(data, filedata, movie)
params['mode'] = 'addfile' req_params['mode'] = 'addfile'
else: else:
params['name'] = data.get('url') req_params['name'] = data.get('url')
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(params)
try: try:
if params.get('mode') is 'addfile': if req_params.get('mode') is 'addfile':
sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True, show_error = False, headers = {'User-Agent': Env.getIdentifier()}) sab_data = self.call(req_params, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True)
else: else:
sab = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}) sab_data = self.call(req_params)
except URLError: except URLError:
log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0)) log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0))
return False return False
@@ -49,17 +46,19 @@ class Sabnzbd(Downloader):
log.error('Failed sending release, use API key, NOT the NZB key: %s', traceback.format_exc(0)) log.error('Failed sending release, use API key, NOT the NZB key: %s', traceback.format_exc(0))
return False return False
result = sab.strip() if sab_data.get('error'):
if not result: log.error('Error getting data from SABNZBd: %s', sab_data.get('error'))
log.error('SABnzbd didn\'t return anything.')
return False return False
log.debug('Result text from SAB: %s', result[:40]) log.debug('Result from SAB: %s', sab_data)
if result[:2] == 'ok': if sab_data.get('status'):
log.info('NZB sent to SAB successfully.') log.info('NZB sent to SAB successfully.')
return True if filedata:
return self.downloadReturnId(sab_data.get('nzo_ids')[0])
else:
return True
else: else:
log.error(result[:40]) log.error(sab_data)
return False return False
def getAllDownloadStatus(self): def getAllDownloadStatus(self):
@@ -85,14 +84,13 @@ class Sabnzbd(Downloader):
log.error('Failed getting history json: %s', traceback.format_exc(1)) log.error('Failed getting history json: %s', traceback.format_exc(1))
return False return False
statuses = [] statuses = StatusList(self)
# Get busy releases # Get busy releases
for item in queue.get('slots', []): for item in queue.get('slots', []):
statuses.append({ statuses.append({
'id': item['nzo_id'], 'id': item['nzo_id'],
'name': item['filename'], 'name': item['filename'],
'status': 'busy',
'original_status': item['status'], 'original_status': item['status'],
'timeleft': item['timeleft'] if not queue['paused'] else -1, 'timeleft': item['timeleft'] if not queue['paused'] else -1,
}) })
@@ -133,21 +131,21 @@ class Sabnzbd(Downloader):
return True return True
def call(self, params, use_json = True): def call(self, request_params, use_json = True, **kwargs):
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(params, { url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(request_params, {
'apikey': self.conf('api_key'), 'apikey': self.conf('api_key'),
'output': 'json' 'output': 'json'
})) }))
data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}) data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}, **kwargs)
if use_json: if use_json:
d = json.loads(data) d = json.loads(data)
if d.get('error'): if d.get('error'):
log.error('Error getting data from SABNZBd: %s', d.get('error')) log.error('Error getting data from SABNZBd: %s', d.get('error'))
return {} return {}
return d[params['mode']] return d.get(request_params['mode']) or d
else: else:
return data return data
@@ -62,7 +62,7 @@ class Transmission(Downloader):
if torrent_params: if torrent_params:
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params) trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
return True return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
except Exception, err: except Exception, err:
log.error('Failed to change settings for transfer: %s', err) log.error('Failed to change settings for transfer: %s', err)
return False return False
@@ -1,6 +1,6 @@
from base64 import b16encode, b32decode from base64 import b16encode, b32decode
from bencode import bencode, bdecode from bencode import bencode, bdecode
from couchpotato.core.downloaders.base import Downloader 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.logger import CPLog from couchpotato.core.logger import CPLog
from hashlib import sha1 from hashlib import sha1
@@ -66,7 +66,7 @@ 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 True return self.downloadReturnId(torrent_hash)
except Exception, err: except Exception, err:
log.error('Failed to send torrent to uTorrent: %s', err) log.error('Failed to send torrent to uTorrent: %s', err)
return False return False
@@ -103,7 +103,7 @@ class uTorrent(Downloader):
log.debug('Nothing in queue') log.debug('Nothing in queue')
return False return False
statuses = [] statuses = StatusList(self)
# Get torrents # Get torrents
for item in queue.get('torrents', []): for item in queue.get('torrents', []):
+1 -1
View File
@@ -110,7 +110,7 @@ def fireEvent(name, *args, **kwargs):
if isinstance(results[0], dict): if isinstance(results[0], dict):
merged = {} merged = {}
for result in results: for result in results:
merged = mergeDicts(merged, result) merged = mergeDicts(merged, result, prepend_list = True)
results = merged results = merged
# Lists # Lists
+2 -2
View File
@@ -53,7 +53,7 @@ def getDataDir():
def isDict(object): def isDict(object):
return isinstance(object, dict) return isinstance(object, dict)
def mergeDicts(a, b): def mergeDicts(a, b, prepend_list = False):
assert isDict(a), isDict(b) assert isDict(a), isDict(b)
dst = a.copy() dst = a.copy()
@@ -67,7 +67,7 @@ def mergeDicts(a, b):
if isDict(current_src[key]) and isDict(current_dst[key]): if isDict(current_src[key]) and isDict(current_dst[key]):
stack.append((current_dst[key], current_src[key])) stack.append((current_dst[key], current_src[key]))
elif isinstance(current_src[key], list) and isinstance(current_dst[key], list): elif isinstance(current_src[key], list) and isinstance(current_dst[key], list):
current_dst[key].extend(current_src[key]) current_dst[key] = current_src[key] + current_dst[key] if prepend_list else current_dst[key] + current_src[key]
current_dst[key] = removeListDuplicates(current_dst[key]) current_dst[key] = removeListDuplicates(current_dst[key])
else: else:
current_dst[key] = current_src[key] current_dst[key] = current_src[key]
+11 -5
View File
@@ -2,13 +2,15 @@ from couchpotato.api import addApiView
from couchpotato.core.event import addEvent from couchpotato.core.event import addEvent
from couchpotato.core.helpers.request import jsonified from couchpotato.core.helpers.request import jsonified
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.providers.base import Provider
from couchpotato.environment import Env from couchpotato.environment import Env
log = CPLog(__name__) log = CPLog(__name__)
class Notification(Plugin): class Notification(Provider):
type = 'notification'
default_title = Env.get('appname') default_title = Env.get('appname')
test_message = 'ZOMG Lazors Pewpewpew!' test_message = 'ZOMG Lazors Pewpewpew!'
@@ -20,7 +22,7 @@ class Notification(Plugin):
dont_listen_to = [] dont_listen_to = []
def __init__(self): def __init__(self):
addEvent('notify.%s' % self.getName().lower(), self.notify) addEvent('notify.%s' % self.getName().lower(), self._notify)
addApiView(self.testNotifyName(), self.test) addApiView(self.testNotifyName(), self.test)
@@ -33,13 +35,17 @@ class Notification(Plugin):
def notify(message = None, group = {}, data = None): def notify(message = None, group = {}, data = None):
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched': if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
return return
return self.notify(message = message, data = data if data else group, listener = listener) return self._notify(message = message, data = data if data else group, listener = listener)
return notify return notify
def getNotificationImage(self, size = 'small'): def getNotificationImage(self, size = 'small'):
return 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/notify.couch.%s.png' % size return 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/notify.couch.%s.png' % size
def _notify(self, *args, **kwargs):
if self.isEnabled():
self.notify(*args, **kwargs)
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
pass pass
@@ -49,7 +55,7 @@ class Notification(Plugin):
log.info('Sending test to %s', test_type) log.info('Sending test to %s', test_type)
success = self.notify( success = self._notify(
message = self.test_message, message = self.test_message,
data = {}, data = {},
listener = 'test' listener = 'test'
@@ -11,7 +11,6 @@ class Boxcar(Notification):
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications' url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
try: try:
message = message.strip() message = message.strip()
@@ -12,7 +12,6 @@ log = CPLog(__name__)
class Email(Notification): class Email(Notification):
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
# Extract all the settings from settings # Extract all the settings from settings
from_address = self.conf('from') from_address = self.conf('from')
@@ -50,6 +49,5 @@ class Email(Notification):
return True return True
except: except:
log.error('E-mail failed: %s', traceback.format_exc()) log.error('E-mail failed: %s', traceback.format_exc())
return False
return False return False
@@ -44,7 +44,6 @@ class Growl(Notification):
log.error('Failed register of growl: %s', traceback.format_exc()) log.error('Failed register of growl: %s', traceback.format_exc())
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
self.register() self.register()
@@ -13,7 +13,6 @@ class Notifo(Notification):
url = 'https://api.notifo.com/v1/send_notification' url = 'https://api.notifo.com/v1/send_notification'
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
try: try:
params = { params = {
@@ -9,7 +9,6 @@ log = CPLog(__name__)
class NotifyMyAndroid(Notification): class NotifyMyAndroid(Notification):
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
nma = pynma.PyNMA() nma = pynma.PyNMA()
keys = splitString(self.conf('api_key')) keys = splitString(self.conf('api_key'))
@@ -9,7 +9,6 @@ log = CPLog(__name__)
class NotifyMyWP(Notification): class NotifyMyWP(Notification):
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
keys = splitString(self.conf('api_key')) keys = splitString(self.conf('api_key'))
p = PyNMWP(keys, self.conf('dev_key')) p = PyNMWP(keys, self.conf('dev_key'))
@@ -46,7 +46,6 @@ class Plex(Notification):
return True return True
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
hosts = [x.strip() + ':3000' for x in self.conf('host').split(",")] hosts = [x.strip() + ':3000' for x in self.conf('host').split(",")]
successful = 0 successful = 0
@@ -13,7 +13,6 @@ class Prowl(Notification):
} }
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
data = { data = {
'apikey': self.conf('api_key'), 'apikey': self.conf('api_key'),
@@ -12,7 +12,6 @@ class Pushalot(Notification):
} }
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
data = { data = {
'AuthorizationToken': self.conf('auth_token'), 'AuthorizationToken': self.conf('auth_token'),
@@ -11,7 +11,6 @@ class Pushover(Notification):
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy' app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
http_handler = HTTPSConnection("api.pushover.net:443") http_handler = HTTPSConnection("api.pushover.net:443")
@@ -12,7 +12,6 @@ class Toasty(Notification):
} }
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
data = { data = {
'title': self.default_title, 'title': self.default_title,
@@ -0,0 +1,30 @@
from .main import Trakt
def start():
return Trakt()
config = [{
'name': 'trakt',
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'trakt',
'label': 'Trakt',
'description': 'add movies to your collection once downloaded. Fill in your username and password in the <a href="../automation/">Automation Trakt settings</a>',
'options': [
{
'name': 'notification_enabled',
'default': False,
'type': 'enabler',
},
{
'name': 'remove_watchlist_enabled',
'label': 'Remove from watchlist',
'default': False,
'type': 'bool',
},
],
}
],
}]
@@ -0,0 +1,46 @@
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
log = CPLog(__name__)
class Trakt(Notification):
urls = {
'base': 'http://api.trakt.tv/%s',
'library': 'movie/library/%s',
'unwatchlist': 'movie/unwatchlist/%s',
}
listen_to = ['movie.downloaded']
def notify(self, message = '', data = {}, listener = None):
post_data = {
'username': self.conf('automation_username'),
'password' : self.conf('automation_password'),
'movies': [{
'imdb_id': data['library']['identifier'],
'title': data['library']['titles'][0]['title'],
'year': data['library']['year']
}] if data else []
}
result = self.call((self.urls['library'] % self.conf('automation_api_key')), post_data)
if self.conf('remove_watchlist_enabled'):
result = result and self.call((self.urls['unwatchlist'] % self.conf('automation_api_key')), post_data)
return result
def call(self, method_url, post_data):
try:
response = self.getJsonData(self.urls['base'] % method_url, params = post_data, cache_timeout = 1)
if response:
if response.get('status') == "success":
log.info('Successfully called Trakt')
return True
except:
pass
log.error('Failed to call trakt, check your login.')
return False
@@ -32,7 +32,6 @@ class Twitter(Notification):
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials) addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret')) api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret'))
@@ -15,7 +15,6 @@ class XBMC(Notification):
use_json_notifications = {} use_json_notifications = {}
def notify(self, message = '', data = {}, listener = None): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
hosts = splitString(self.conf('host')) hosts = splitString(self.conf('host'))
+2 -1
View File
@@ -370,6 +370,7 @@ class MoviePlugin(Plugin):
status_active = fireEvent('status.add', 'active', single = True) status_active = fireEvent('status.add', 'active', single = True)
snatched_status = fireEvent('status.add', 'snatched', single = True) snatched_status = fireEvent('status.add', 'snatched', single = True)
ignored_status = fireEvent('status.add', 'ignored', single = True) ignored_status = fireEvent('status.add', 'ignored', single = True)
done_status = fireEvent('status.add', 'done', single = True)
downloaded_status = fireEvent('status.add', 'downloaded', single = True) downloaded_status = fireEvent('status.add', 'downloaded', single = True)
default_profile = fireEvent('profile.default', single = True) default_profile = fireEvent('profile.default', single = True)
@@ -397,7 +398,7 @@ class MoviePlugin(Plugin):
# Clean snatched history # Clean snatched history
for release in m.releases: for release in m.releases:
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id')]: if release.status_id in [downloaded_status.get('id'), snatched_status.get('id'), done_status.get('id')]:
if params.get('ignore_previous', False): if params.get('ignore_previous', False):
release.status_id = ignored_status.get('id') release.status_id = ignored_status.get('id')
else: else:
@@ -94,36 +94,30 @@ MA.Release = new Class({
} }
else { else {
var buttons_done = false; var releases = self.movie.data.releases.sortBy('-info.score');
self.movie.data.releases.sortBy('-info.score').each(function(release){ for(x in releases){
if(buttons_done) return; var release = releases[x],
status = Status.get(release.status_id);
var status = Status.get(release.status_id);
if((self.next_release && (status.identifier == 'ignored' || status.identifier == 'failed')) || (!self.next_release && status.identifier == 'available')){ if((self.next_release && (status.identifier == 'ignored' || status.identifier == 'failed')) || (!self.next_release && status.identifier == 'available')){
self.hide_on_click = false; self.showHelper();
self.show(); break;
buttons_done = true;
} }
}
});
} }
}, },
show: function(e){ createReleases: function(){
var self = this; var self = this;
if(e)
(e).preventDefault();
if(!self.options_container){ if(!self.options_container){
self.options_container = new Element('div.options').adopt( self.options_container = new Element('div.options').adopt(
self.release_container = new Element('div.releases.table').adopt( self.release_container = new Element('div.releases.table').adopt(
self.trynext_container = new Element('div.buttons.try_container') self.trynext_container = new Element('div.buttons.try_container')
) )
).inject(self.movie, 'top'); );
// Header // Header
new Element('div.item.head').adopt( new Element('div.item.head').adopt(
@@ -238,9 +232,71 @@ MA.Release = new Class({
} }
},
show: function(e){
var self = this;
if(e)
(e).preventDefault();
self.createReleases();
self.options_container.inject(self.movie, 'top');
self.movie.slide('in', self.options_container); self.movie.slide('in', self.options_container);
}, },
showHelper: function(e){
var self = this;
if(e)
(e).preventDefault();
self.createReleases();
self.trynext_container = new Element('div.buttons.trynext').inject(self.movie.info_container);
if(self.next_release || self.last_release){
self.trynext_container.adopt(
self.next_release ? [new Element('a.icon.readd', {
'text': self.last_release ? 'Download another release' : 'Download the best release',
'events': {
'click': self.tryNextRelease.bind(self)
}
}),
new Element('a.icon.download', {
'text': 'pick one yourself',
'events': {
'click': function(){
self.movie.quality.fireEvent('click');
}
}
})] : null,
new Element('a.icon.completed', {
'text': 'mark this movie done',
'events': {
'click': function(){
Api.request('movie.delete', {
'data': {
'id': self.movie.get('id'),
'delete_from': 'wanted'
},
'onComplete': function(){
var movie = $(self.movie);
movie.set('tween', {
'duration': 300,
'onComplete': function(){
self.movie.destroy()
}
});
movie.tween('height', 0);
}
});
}
}
})
)
}
},
get: function(release, type){ get: function(release, type){
return release.info[type] || 'n/a' return release.info[type] || 'n/a'
}, },
@@ -251,14 +307,15 @@ MA.Release = new Class({
var release_el = self.release_container.getElement('#release_'+release.id), var release_el = self.release_container.getElement('#release_'+release.id),
icon = release_el.getElement('.download.icon'); icon = release_el.getElement('.download.icon');
icon.addClass('spinner'); self.movie.busy(true);
Api.request('release.download', { Api.request('release.download', {
'data': { 'data': {
'id': release.id 'id': release.id
}, },
'onComplete': function(json){ 'onComplete': function(json){
icon.removeClass('spinner') self.movie.busy(false);
if(json.success) if(json.success)
icon.addClass('completed'); icon.addClass('completed');
else else
@@ -281,6 +338,8 @@ MA.Release = new Class({
tryNextRelease: function(movie_id){ tryNextRelease: function(movie_id){
var self = this; var self = this;
self.createReleases();
if(self.last_release) if(self.last_release)
self.ignore(self.last_release); self.ignore(self.last_release);
@@ -39,7 +39,7 @@
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
height: 180px; height: 180px;
transition: all 0.2s linear; transition: all 0.6s cubic-bezier(0.9,0,0.1,1);
} }
.movies.list_list .movie:not(.details_view), .movies.list_list .movie:not(.details_view),
@@ -95,7 +95,7 @@
width: 938px; width: 938px;
box-shadow: none; box-shadow: none;
border: 0; border: 0;
background: none; background: #4e5969;
} }
.movies.thumbs_list .data { .movies.thumbs_list .data {
@@ -322,8 +322,8 @@
right: 10px; right: 10px;
} }
.movies .data:hover .action { opacity: 0.6; } .movies .movie:hover .action { opacity: 0.6; }
.movies .data:hover .action:hover { opacity: 1; } .movies .movie:hover .action:hover { opacity: 1; }
.movies.mass_edit_list .data .actions { .movies.mass_edit_list .data .actions {
display: none; display: none;
} }
@@ -338,8 +338,8 @@
opacity: 0; opacity: 0;
} }
.movies.list_list .movie:not(.details_view) .data:hover .actions, .movies.list_list .movie:not(.details_view):hover .actions,
.movies.mass_edit_list .data:hover .actions { .movies.mass_edit_list .movie:hover .actions {
margin: 0; margin: 0;
background: #4e5969; background: #4e5969;
top: 2px; top: 2px;
@@ -510,6 +510,29 @@
.movies .movie .releases .last_release > :first-child { .movies .movie .releases .last_release > :first-child {
margin-left: -6px; margin-left: -6px;
} }
.movies .movie .trynext {
display: inline;
position: absolute;
right: 135px;
z-index: 2;
opacity: 0;
text-shadow: none;
background: #4e5969;
}
.movies .movie:hover .trynext {
opacity: 1;
}
.movies .movie .trynext a {
background-position: 5px center;
padding: 0 5px 0 25px;
margin-right: 10px;
color: #FFF;
border-radius: 2px;
}
.movies .movie .trynext a:hover {
background-color: #369545;
}
.movies .load_more { .movies .load_more {
display: block; display: block;
+2 -2
View File
@@ -19,8 +19,8 @@ class QualityPlugin(Plugin):
qualities = [ qualities = [
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]}, {'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
{'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']}, {'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']},
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']}, {'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi']}, {'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi']},
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']}, {'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]}, {'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
+13 -3
View File
@@ -160,7 +160,9 @@ class Release(Plugin):
db = get_session() db = get_session()
id = getParam('id') id = getParam('id')
status_snatched = fireEvent('status.add', 'snatched', single = True)
snatched_status = fireEvent('status.add', 'snatched', single = True)
done_status = fireEvent('status.get', 'done', single = True)
rel = db.query(Relea).filter_by(id = id).first() rel = db.query(Relea).filter_by(id = id).first()
if rel: if rel:
@@ -168,6 +170,8 @@ class Release(Plugin):
for info in rel.info: for info in rel.info:
item[info.identifier] = info.value item[info.identifier] = info.value
fireEvent('notify.frontend', type = 'release.download', data = True, message = 'Snatching "%s"' % item['name'])
# Get matching provider # Get matching provider
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True) provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
@@ -182,8 +186,14 @@ class Release(Plugin):
}), manual = True, single = True) }), manual = True, single = True)
if success: if success:
rel.status_id = status_snatched.get('id') db.expunge_all()
db.commit() rel = db.query(Relea).filter_by(id = id).first() # Get release again
if rel.status_id != done_status.get('id'):
rel.status_id = snatched_status.get('id')
db.commit()
fireEvent('notify.frontend', type = 'release.download', data = True, message = 'Successfully snatched "%s"' % item['name'])
return jsonified({ return jsonified({
'success': success 'success': success
+1 -1
View File
@@ -13,7 +13,7 @@ rename_options = {
'thename': 'The Moviename', 'thename': 'The Moviename',
'year': 'Year (2011)', 'year': 'Year (2011)',
'first': 'First letter (M)', 'first': 'First letter (M)',
'quality': 'Quality (720P)', 'quality': 'Quality (720p)',
'video': 'Video (x264)', 'video': 'Video (x264)',
'audio': 'Audio (DTS)', 'audio': 'Audio (DTS)',
'group': 'Releasegroup name', 'group': 'Releasegroup name',
+9 -1
View File
@@ -571,8 +571,16 @@ class Renamer(Plugin):
found = False found = False
for item in statuses: for item in statuses:
if item['name'] == nzbname or rel_dict['info']['name'] in item['name'] or getImdb(item['name']) == movie_dict['library']['identifier']: found_release = False
if rel_dict['info'].get('download_id'):
if item['id'] == rel_dict['info']['download_id'] and item['downloader'] == rel_dict['info']['download_downloader']:
log.debug('Found release by id: %s', item['id'])
found_release = True
else:
if item['name'] == nzbname or rel_dict['info']['name'] in item['name'] or getImdb(item['name']) == movie_dict['library']['identifier']:
found_release = True
if found_release:
timeleft = 'N/A' if item['timeleft'] == -1 else item['timeleft'] timeleft = 'N/A' if item['timeleft'] == -1 else item['timeleft']
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))
+7 -1
View File
@@ -11,6 +11,7 @@ from subliminal.videos import Video
import enzyme import enzyme
import os import os
import re import re
import threading
import time import time
import traceback import traceback
@@ -74,7 +75,7 @@ class Scanner(Plugin):
'hdtv': ['hdtv'] 'hdtv': ['hdtv']
} }
clean = '[ _\,\.\(\)\[\]\-](french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)' clean = '[ _\,\.\(\)\[\]\-](extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
multipart_regex = [ multipart_regex = [
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1 '[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1 '[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
@@ -388,6 +389,11 @@ class Scanner(Plugin):
if on_found: if on_found:
on_found(group, total_found, total_found - len(processed_movies)) on_found(group, total_found, total_found - len(processed_movies))
# Wait for all the async events calm down a bit
while threading.activeCount() > 100 and not self.shuttingDown():
log.debug('Too many threads active, waiting a few seconds')
time.sleep(10)
if len(processed_movies) > 0: if len(processed_movies) > 0:
log.info('Found %s movies in the folder %s', (len(processed_movies), folder)) log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
else: else:
+13 -4
View File
@@ -285,10 +285,10 @@ class Searcher(Plugin):
if filedata == 'try_next': if filedata == 'try_next':
return filedata return filedata
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True) download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
log.debug('Downloader result: %s', download_result)
if successful:
if download_result:
try: try:
# Mark release as snatched # Mark release as snatched
db = get_session() db = get_session()
@@ -298,6 +298,15 @@ class Searcher(Plugin):
done_status = fireEvent('status.get', 'done', single = True) done_status = fireEvent('status.get', 'done', single = True)
rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id') rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
# Save download-id info if returned
if isinstance(download_result, dict):
for key in download_result:
rls_info = ReleaseInfo(
identifier = 'download_%s' % key,
value = toUnicode(download_result.get(key))
)
rls.info.append(rls_info)
db.commit() db.commit()
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label) log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
@@ -333,7 +342,7 @@ class Searcher(Plugin):
return True return True
log.info('Tried to download, but none of the "%s" downloaders are enabled', (data.get('type', ''))) log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('type', '')))
return False return False
@@ -20,7 +20,7 @@ config = [{
}, },
{ {
'name': 'languages', 'name': 'languages',
'description': 'Comma separated, 2 letter country code. Example: en, nl', 'description': 'Comma separated, 2 letter country code. Example: en, nl. See the codes at <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">on Wikipedia</a>',
}, },
# { # {
# 'name': 'automatic', # 'name': 'automatic',
+1 -1
View File
@@ -22,7 +22,7 @@ config = [{
'name': 'quality', 'name': 'quality',
'default': '720p', 'default': '720p',
'type': 'dropdown', 'type': 'dropdown',
'values': [('1080P', '1080p'), ('720P', '720p'), ('480P', '480p')], 'values': [('1080p', '1080p'), ('720p', '720p'), ('480P', '480p')],
}, },
{ {
'name': 'name', 'name': 'name',
@@ -0,0 +1,34 @@
from .main import Letterboxd
def start():
return Letterboxd()
config = [{
'name': 'letterboxd',
'groups': [
{
'tab': 'automation',
'list': 'watchlist_providers',
'name': 'letterboxd_automation',
'label': 'Letterboxd',
'description': 'Import movies from any public <a href="http://letterboxd.com/">Letterboxd</a> watchlist',
'options': [
{
'name': 'automation_enabled',
'default': False,
'type': 'enabler',
},
{
'name': 'automation_urls_use',
'label': 'Use',
},
{
'name': 'automation_urls',
'label': 'Username',
'type': 'combined',
'combine': ['automation_urls_use', 'automation_urls'],
},
],
},
],
}]
@@ -0,0 +1,49 @@
from bs4 import BeautifulSoup
from couchpotato.core.helpers.variable import tryInt, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.automation.base import Automation
import re
log = CPLog(__name__)
class Letterboxd(Automation):
url = 'http://letterboxd.com/%s/watchlist/'
pattern = re.compile(r'(.*)\((\d*)\)')
def getIMDBids(self):
urls = splitString(self.conf('automation_urls'))
if len(urls) == 0:
return []
movies = []
for movie in self.getWatchlist():
imdb_id = self.search(movie.get('title'), movie.get('year'), imdb_only = True)
movies.append(imdb_id)
return movies
def getWatchlist(self):
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
urls = splitString(self.conf('automation_urls'))
index = -1
movies = []
for username in urls:
index += 1
if not enablers[index]:
continue
soup = BeautifulSoup(self.getHTMLData(self.url % username))
for movie in soup.find_all('a', attrs = { 'class': 'frame' }):
match = filter(None, self.pattern.split(movie['title']))
movies.append({'title': match[0], 'year': match[1] })
return movies
+16 -10
View File
@@ -76,7 +76,7 @@ class LibraryTitle(Entity):
title = Field(Unicode) title = Field(Unicode)
simple_title = Field(Unicode, index = True) simple_title = Field(Unicode, index = True)
default = Field(Boolean, index = True) default = Field(Boolean, default = False, index = True)
language = OneToMany('Language') language = OneToMany('Language')
libraries = ManyToOne('Library') libraries = ManyToOne('Library')
@@ -141,12 +141,12 @@ class Status(Entity):
class Quality(Entity): class Quality(Entity):
"""Quality name of a release, DVD, 720P, DVD-Rip etc""" """Quality name of a release, DVD, 720p, DVD-Rip etc"""
using_options(order_by = 'order') using_options(order_by = 'order')
identifier = Field(String(20), unique = True) identifier = Field(String(20), unique = True)
label = Field(Unicode(20)) label = Field(Unicode(20))
order = Field(Integer, index = True) order = Field(Integer, default = 0, index = True)
size_min = Field(Integer) size_min = Field(Integer)
size_max = Field(Integer) size_max = Field(Integer)
@@ -160,21 +160,27 @@ class Profile(Entity):
using_options(order_by = 'order') using_options(order_by = 'order')
label = Field(Unicode(50)) label = Field(Unicode(50))
order = Field(Integer, index = True) order = Field(Integer, default = 0, index = True)
core = Field(Boolean) core = Field(Boolean, default = False)
hide = Field(Boolean) hide = Field(Boolean, default = False)
movie = OneToMany('Movie') movie = OneToMany('Movie')
types = OneToMany('ProfileType', cascade = 'all, delete-orphan') types = OneToMany('ProfileType', cascade = 'all, delete-orphan')
def to_dict(self, deep = {}, exclude = []):
orig_dict = super(Profile, self).to_dict(deep = deep, exclude = exclude)
orig_dict['core'] = orig_dict.get('core') or False
orig_dict['hide'] = orig_dict.get('hide') or False
return orig_dict
class ProfileType(Entity): class ProfileType(Entity):
"""""" """"""
using_options(order_by = 'order') using_options(order_by = 'order')
order = Field(Integer, index = True) order = Field(Integer, default = 0, index = True)
finish = Field(Boolean) finish = Field(Boolean, default = True)
wait_for = Field(Integer) wait_for = Field(Integer, default = 0)
quality = ManyToOne('Quality') quality = ManyToOne('Quality')
profile = ManyToOne('Profile') profile = ManyToOne('Profile')
@@ -185,7 +191,7 @@ class File(Entity):
path = Field(Unicode(255), nullable = False, unique = True) path = Field(Unicode(255), nullable = False, unique = True)
part = Field(Integer, default = 1) part = Field(Integer, default = 1)
available = Field(Boolean) available = Field(Boolean, default = True)
type = ManyToOne('FileType') type = ManyToOne('FileType')
properties = OneToMany('FileProperty') properties = OneToMany('FileProperty')
+6 -3
View File
@@ -190,9 +190,12 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
version_control(db, repo, version = latest_db_version) version_control(db, repo, version = latest_db_version)
current_db_version = db_version(db, repo) current_db_version = db_version(db, repo)
if current_db_version < latest_db_version and not development: if current_db_version < latest_db_version:
log.info('Doing database upgrade. From %d to %d', (current_db_version, latest_db_version)) if development:
upgrade(db, repo) log.error('There is a database migration ready, but you are running development mode, so it won\'t be used. If you see this, you are stupid. Please disable development mode.')
else:
log.info('Doing database upgrade. From %d to %d', (current_db_version, latest_db_version))
upgrade(db, repo)
# Configure Database # Configure Database
from couchpotato.core.settings.model import setup from couchpotato.core.settings.model import setup
+1 -1
View File
@@ -23,7 +23,7 @@ Page.Home = new Class({
'identifier': 'snatched', 'identifier': 'snatched',
'load_more': false, 'load_more': false,
'view': 'list', 'view': 'list',
'actions': [MA.IMDB, MA.Trailer, MA.Files, MA.Release, MA.Edit, MA.Readd, MA.Refresh, MA.Delete], 'actions': [MA.IMDB, MA.Trailer, MA.Release, MA.Refresh, MA.Delete],
'title': 'Snatched & Available', 'title': 'Snatched & Available',
'on_empty_element': new Element('div'), 'on_empty_element': new Element('div'),
'filter': { 'filter': {
+4 -2
View File
@@ -1283,6 +1283,7 @@ Option.Combined = new Class({
self.values = {} self.values = {}
self.inputs = {} self.inputs = {}
self.items = [] self.items = []
self.labels = {}
self.options.combine.each(function(name){ self.options.combine.each(function(name){
@@ -1302,9 +1303,10 @@ Option.Combined = new Class({
var head = new Element('div.head').inject(self.combined_list) var head = new Element('div.head').inject(self.combined_list)
Object.each(self.inputs, function(input, name){ Object.each(self.inputs, function(input, name){
self.labels[name] = input.getPrevious().get('text')
new Element('abbr', { new Element('abbr', {
'class': name, 'class': name,
'text': input.getPrevious().get('text'), 'text': self.labels[name],
//'title': input.getNext().get('text') //'title': input.getNext().get('text')
}).inject(head) }).inject(head)
}) })
@@ -1367,7 +1369,7 @@ Option.Combined = new Class({
value_count++; value_count++;
new Element('input[type=text].inlay.'+name, { new Element('input[type=text].inlay.'+name, {
'value': value, 'value': value,
'placeholder': name, 'placeholder': self.labels[name] || name,
'events': { 'events': {
'keyup': self.saveCombined.bind(self), 'keyup': self.saveCombined.bind(self),
'change': self.saveCombined.bind(self) 'change': self.saveCombined.bind(self)
+2 -3
View File
@@ -134,10 +134,9 @@ body > .spinner, .mask{
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
line-height: 1; line-height: 1;
border-radius: 5px; border-radius: 2px;
box-shadow: 0 1px 3px rgba(0,0,0,0.5); box-shadow: 0 1px 2px rgba(0,0,0,0.3);
text-shadow: 0 -1px 1px rgba(0,0,0,0.25); text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
border-bottom: 1px solid rgba(0,0,0,0.25);
cursor: pointer; cursor: pointer;
} }
.button.red { background-color: #ff0000; } .button.red { background-color: #ff0000; }