diff --git a/couchpotato/core/_base/_core/__init__.py b/couchpotato/core/_base/_core/__init__.py
index c8c3fda6..4d1a6840 100644
--- a/couchpotato/core/_base/_core/__init__.py
+++ b/couchpotato/core/_base/_core/__init__.py
@@ -70,7 +70,7 @@ config = [{
'name': 'development',
'default': 0,
'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',
diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py
index 70500dc0..69449249 100644
--- a/couchpotato/core/downloaders/base.py
+++ b/couchpotato/core/downloaders/base.py
@@ -1,5 +1,6 @@
from base64 import b32decode, b16encode
from couchpotato.core.event import addEvent
+from couchpotato.core.helpers.variable import mergeDicts
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.base import Provider
import random
@@ -103,6 +104,12 @@ class Downloader(Provider):
log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
return False
+ def downloadReturnId(self, download_id):
+ return {
+ 'downloader': self.getName(),
+ 'id': download_id
+ }
+
def isDisabled(self, manual, data):
return not self.isEnabled(manual, data)
@@ -116,3 +123,34 @@ class Downloader(Provider):
return super(Downloader, self).isEnabled() and \
((d_manual and manual) or (d_manual is False)) and \
(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)
+
diff --git a/couchpotato/core/downloaders/nzbget/__init__.py b/couchpotato/core/downloaders/nzbget/__init__.py
index 403a7e7d..5349914a 100644
--- a/couchpotato/core/downloaders/nzbget/__init__.py
+++ b/couchpotato/core/downloaders/nzbget/__init__.py
@@ -48,6 +48,12 @@ config = [{
'advanced': True,
'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.',
+ },
],
}
],
diff --git a/couchpotato/core/downloaders/nzbget/main.py b/couchpotato/core/downloaders/nzbget/main.py
index 82d8a363..1bc54fd5 100644
--- a/couchpotato/core/downloaders/nzbget/main.py
+++ b/couchpotato/core/downloaders/nzbget/main.py
@@ -1,9 +1,11 @@
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.variable import tryInt
from couchpotato.core.logger import CPLog
+from datetime import timedelta
import re
+import shutil
import socket
import traceback
import xmlrpclib
@@ -50,7 +52,107 @@ class NZBGet(Downloader):
if xml_response:
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:
log.error('NZBGet could not add %s to the queue.', nzb_name)
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
diff --git a/couchpotato/core/downloaders/nzbvortex/main.py b/couchpotato/core/downloaders/nzbvortex/main.py
index 1462c678..e6cbd027 100644
--- a/couchpotato/core/downloaders/nzbvortex/main.py
+++ b/couchpotato/core/downloaders/nzbvortex/main.py
@@ -1,5 +1,5 @@
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.variable import cleanHost
from couchpotato.core.logger import CPLog
@@ -29,7 +29,9 @@ class NZBVortex(Downloader):
nzb_filename = self.createFileName(data, filedata, movie)
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:
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
return False
@@ -38,7 +40,7 @@ class NZBVortex(Downloader):
raw_statuses = self.call('nzb')
- statuses = []
+ statuses = StatusList(self)
for item in raw_statuses.get('nzbs', []):
# Check status
diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py
index a287f119..8b374698 100644
--- a/couchpotato/core/downloaders/sabnzbd/main.py
+++ b/couchpotato/core/downloaders/sabnzbd/main.py
@@ -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.variable import cleanHost, mergeDicts
from couchpotato.core.logger import CPLog
@@ -17,8 +17,7 @@ class Sabnzbd(Downloader):
log.info('Sending "%s" to SABnzbd.', data.get('name'))
- params = {
- 'apikey': self.conf('api_key'),
+ req_params = {
'cat': self.conf('category'),
'mode': 'addurl',
'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
nzb_filename = self.createFileName(data, filedata, movie)
- params['mode'] = 'addfile'
+ req_params['mode'] = 'addfile'
else:
- params['name'] = data.get('url')
-
- url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(params)
+ req_params['name'] = data.get('url')
try:
- if 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()})
+ if req_params.get('mode') is 'addfile':
+ sab_data = self.call(req_params, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True)
else:
- sab = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
+ sab_data = self.call(req_params)
except URLError:
log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0))
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))
return False
- result = sab.strip()
- if not result:
- log.error('SABnzbd didn\'t return anything.')
+ if sab_data.get('error'):
+ log.error('Error getting data from SABNZBd: %s', sab_data.get('error'))
return False
- log.debug('Result text from SAB: %s', result[:40])
- if result[:2] == 'ok':
+ log.debug('Result from SAB: %s', sab_data)
+ if sab_data.get('status'):
log.info('NZB sent to SAB successfully.')
- return True
+ if filedata:
+ return self.downloadReturnId(sab_data.get('nzo_ids')[0])
+ else:
+ return True
else:
- log.error(result[:40])
+ log.error(sab_data)
return False
def getAllDownloadStatus(self):
@@ -85,14 +84,13 @@ class Sabnzbd(Downloader):
log.error('Failed getting history json: %s', traceback.format_exc(1))
return False
- statuses = []
+ statuses = StatusList(self)
# Get busy releases
for item in queue.get('slots', []):
statuses.append({
'id': item['nzo_id'],
'name': item['filename'],
- 'status': 'busy',
'original_status': item['status'],
'timeleft': item['timeleft'] if not queue['paused'] else -1,
})
@@ -133,21 +131,21 @@ class Sabnzbd(Downloader):
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'),
'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:
d = json.loads(data)
if d.get('error'):
log.error('Error getting data from SABNZBd: %s', d.get('error'))
return {}
- return d[params['mode']]
+ return d.get(request_params['mode']) or d
else:
return data
diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py
index 5c13af6e..6c4607fb 100644
--- a/couchpotato/core/downloaders/transmission/main.py
+++ b/couchpotato/core/downloaders/transmission/main.py
@@ -62,7 +62,7 @@ class Transmission(Downloader):
if 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:
log.error('Failed to change settings for transfer: %s', err)
return False
diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py
index 5953b117..c64db135 100644
--- a/couchpotato/core/downloaders/utorrent/main.py
+++ b/couchpotato/core/downloaders/utorrent/main.py
@@ -1,6 +1,6 @@
from base64 import b16encode, b32decode
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.logger import CPLog
from hashlib import sha1
@@ -66,7 +66,7 @@ class uTorrent(Downloader):
self.utorrent_api.set_torrent(torrent_hash, torrent_params)
if self.conf('paused', default = 0):
self.utorrent_api.pause_torrent(torrent_hash)
- return True
+ return self.downloadReturnId(torrent_hash)
except Exception, err:
log.error('Failed to send torrent to uTorrent: %s', err)
return False
@@ -103,7 +103,7 @@ class uTorrent(Downloader):
log.debug('Nothing in queue')
return False
- statuses = []
+ statuses = StatusList(self)
# Get torrents
for item in queue.get('torrents', []):
diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py
index bd704f5c..19e97541 100644
--- a/couchpotato/core/event.py
+++ b/couchpotato/core/event.py
@@ -110,7 +110,7 @@ def fireEvent(name, *args, **kwargs):
if isinstance(results[0], dict):
merged = {}
for result in results:
- merged = mergeDicts(merged, result)
+ merged = mergeDicts(merged, result, prepend_list = True)
results = merged
# Lists
diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py
index 82bf88f7..ff1043bb 100644
--- a/couchpotato/core/helpers/variable.py
+++ b/couchpotato/core/helpers/variable.py
@@ -53,7 +53,7 @@ def getDataDir():
def isDict(object):
return isinstance(object, dict)
-def mergeDicts(a, b):
+def mergeDicts(a, b, prepend_list = False):
assert isDict(a), isDict(b)
dst = a.copy()
@@ -67,7 +67,7 @@ def mergeDicts(a, b):
if isDict(current_src[key]) and isDict(current_dst[key]):
stack.append((current_dst[key], current_src[key]))
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])
else:
current_dst[key] = current_src[key]
diff --git a/couchpotato/core/notifications/base.py b/couchpotato/core/notifications/base.py
index 2604ef47..dda3dade 100644
--- a/couchpotato/core/notifications/base.py
+++ b/couchpotato/core/notifications/base.py
@@ -2,13 +2,15 @@ from couchpotato.api import addApiView
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.request import jsonified
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
log = CPLog(__name__)
-class Notification(Plugin):
+class Notification(Provider):
+
+ type = 'notification'
default_title = Env.get('appname')
test_message = 'ZOMG Lazors Pewpewpew!'
@@ -20,7 +22,7 @@ class Notification(Plugin):
dont_listen_to = []
def __init__(self):
- addEvent('notify.%s' % self.getName().lower(), self.notify)
+ addEvent('notify.%s' % self.getName().lower(), self._notify)
addApiView(self.testNotifyName(), self.test)
@@ -33,13 +35,17 @@ class Notification(Plugin):
def notify(message = None, group = {}, data = None):
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
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
def getNotificationImage(self, size = 'small'):
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):
pass
@@ -49,7 +55,7 @@ class Notification(Plugin):
log.info('Sending test to %s', test_type)
- success = self.notify(
+ success = self._notify(
message = self.test_message,
data = {},
listener = 'test'
diff --git a/couchpotato/core/notifications/boxcar/main.py b/couchpotato/core/notifications/boxcar/main.py
index 3135614a..b30d487a 100644
--- a/couchpotato/core/notifications/boxcar/main.py
+++ b/couchpotato/core/notifications/boxcar/main.py
@@ -11,7 +11,6 @@ class Boxcar(Notification):
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
try:
message = message.strip()
diff --git a/couchpotato/core/notifications/email/main.py b/couchpotato/core/notifications/email/main.py
index 014b5856..21fcf157 100644
--- a/couchpotato/core/notifications/email/main.py
+++ b/couchpotato/core/notifications/email/main.py
@@ -12,7 +12,6 @@ log = CPLog(__name__)
class Email(Notification):
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
# Extract all the settings from settings
from_address = self.conf('from')
@@ -50,6 +49,5 @@ class Email(Notification):
return True
except:
log.error('E-mail failed: %s', traceback.format_exc())
- return False
return False
diff --git a/couchpotato/core/notifications/growl/main.py b/couchpotato/core/notifications/growl/main.py
index 7f1398d9..caad661b 100644
--- a/couchpotato/core/notifications/growl/main.py
+++ b/couchpotato/core/notifications/growl/main.py
@@ -44,7 +44,6 @@ class Growl(Notification):
log.error('Failed register of growl: %s', traceback.format_exc())
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
self.register()
diff --git a/couchpotato/core/notifications/notifo/main.py b/couchpotato/core/notifications/notifo/main.py
index 64eee5ec..312055a8 100644
--- a/couchpotato/core/notifications/notifo/main.py
+++ b/couchpotato/core/notifications/notifo/main.py
@@ -13,7 +13,6 @@ class Notifo(Notification):
url = 'https://api.notifo.com/v1/send_notification'
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
try:
params = {
diff --git a/couchpotato/core/notifications/notifymyandroid/main.py b/couchpotato/core/notifications/notifymyandroid/main.py
index cc6ef662..2c4ac90c 100644
--- a/couchpotato/core/notifications/notifymyandroid/main.py
+++ b/couchpotato/core/notifications/notifymyandroid/main.py
@@ -9,7 +9,6 @@ log = CPLog(__name__)
class NotifyMyAndroid(Notification):
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
nma = pynma.PyNMA()
keys = splitString(self.conf('api_key'))
diff --git a/couchpotato/core/notifications/notifymywp/main.py b/couchpotato/core/notifications/notifymywp/main.py
index 3258e85f..167b6eeb 100644
--- a/couchpotato/core/notifications/notifymywp/main.py
+++ b/couchpotato/core/notifications/notifymywp/main.py
@@ -9,7 +9,6 @@ log = CPLog(__name__)
class NotifyMyWP(Notification):
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
keys = splitString(self.conf('api_key'))
p = PyNMWP(keys, self.conf('dev_key'))
diff --git a/couchpotato/core/notifications/plex/main.py b/couchpotato/core/notifications/plex/main.py
index 9790ccfe..95139eed 100644
--- a/couchpotato/core/notifications/plex/main.py
+++ b/couchpotato/core/notifications/plex/main.py
@@ -46,7 +46,6 @@ class Plex(Notification):
return True
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
hosts = [x.strip() + ':3000' for x in self.conf('host').split(",")]
successful = 0
diff --git a/couchpotato/core/notifications/prowl/main.py b/couchpotato/core/notifications/prowl/main.py
index 0990d0d1..e5c4678b 100644
--- a/couchpotato/core/notifications/prowl/main.py
+++ b/couchpotato/core/notifications/prowl/main.py
@@ -13,7 +13,6 @@ class Prowl(Notification):
}
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
data = {
'apikey': self.conf('api_key'),
diff --git a/couchpotato/core/notifications/pushalot/main.py b/couchpotato/core/notifications/pushalot/main.py
index c35efa5c..4c5e76c3 100644
--- a/couchpotato/core/notifications/pushalot/main.py
+++ b/couchpotato/core/notifications/pushalot/main.py
@@ -12,7 +12,6 @@ class Pushalot(Notification):
}
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
data = {
'AuthorizationToken': self.conf('auth_token'),
diff --git a/couchpotato/core/notifications/pushover/main.py b/couchpotato/core/notifications/pushover/main.py
index bcd3245c..8df4ed1f 100644
--- a/couchpotato/core/notifications/pushover/main.py
+++ b/couchpotato/core/notifications/pushover/main.py
@@ -11,7 +11,6 @@ class Pushover(Notification):
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
http_handler = HTTPSConnection("api.pushover.net:443")
diff --git a/couchpotato/core/notifications/toasty/main.py b/couchpotato/core/notifications/toasty/main.py
index 638c75dd..79b021e2 100644
--- a/couchpotato/core/notifications/toasty/main.py
+++ b/couchpotato/core/notifications/toasty/main.py
@@ -12,7 +12,6 @@ class Toasty(Notification):
}
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
data = {
'title': self.default_title,
diff --git a/couchpotato/core/notifications/trakt/__init__.py b/couchpotato/core/notifications/trakt/__init__.py
new file mode 100644
index 00000000..b119736c
--- /dev/null
+++ b/couchpotato/core/notifications/trakt/__init__.py
@@ -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 Automation Trakt settings',
+ 'options': [
+ {
+ 'name': 'notification_enabled',
+ 'default': False,
+ 'type': 'enabler',
+ },
+ {
+ 'name': 'remove_watchlist_enabled',
+ 'label': 'Remove from watchlist',
+ 'default': False,
+ 'type': 'bool',
+ },
+ ],
+ }
+ ],
+}]
diff --git a/couchpotato/core/notifications/trakt/main.py b/couchpotato/core/notifications/trakt/main.py
new file mode 100644
index 00000000..86d47086
--- /dev/null
+++ b/couchpotato/core/notifications/trakt/main.py
@@ -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
diff --git a/couchpotato/core/notifications/twitter/main.py b/couchpotato/core/notifications/twitter/main.py
index bbb9262b..2db19743 100644
--- a/couchpotato/core/notifications/twitter/main.py
+++ b/couchpotato/core/notifications/twitter/main.py
@@ -32,7 +32,6 @@ class Twitter(Notification):
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
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'))
diff --git a/couchpotato/core/notifications/xbmc/main.py b/couchpotato/core/notifications/xbmc/main.py
index 0187a833..84b9f7e4 100755
--- a/couchpotato/core/notifications/xbmc/main.py
+++ b/couchpotato/core/notifications/xbmc/main.py
@@ -15,7 +15,6 @@ class XBMC(Notification):
use_json_notifications = {}
def notify(self, message = '', data = {}, listener = None):
- if self.isDisabled(): return
hosts = splitString(self.conf('host'))
diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py
index 5ec968c3..0a99fe6a 100644
--- a/couchpotato/core/plugins/movie/main.py
+++ b/couchpotato/core/plugins/movie/main.py
@@ -370,6 +370,7 @@ class MoviePlugin(Plugin):
status_active = fireEvent('status.add', 'active', single = True)
snatched_status = fireEvent('status.add', 'snatched', 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)
default_profile = fireEvent('profile.default', single = True)
@@ -397,7 +398,7 @@ class MoviePlugin(Plugin):
# Clean snatched history
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):
release.status_id = ignored_status.get('id')
else:
diff --git a/couchpotato/core/plugins/movie/static/movie.actions.js b/couchpotato/core/plugins/movie/static/movie.actions.js
index a56e9abd..2b7f76d0 100644
--- a/couchpotato/core/plugins/movie/static/movie.actions.js
+++ b/couchpotato/core/plugins/movie/static/movie.actions.js
@@ -94,36 +94,30 @@ MA.Release = new Class({
}
else {
- var buttons_done = false;
+ var releases = self.movie.data.releases.sortBy('-info.score');
- self.movie.data.releases.sortBy('-info.score').each(function(release){
- if(buttons_done) return;
-
- var status = Status.get(release.status_id);
+ for(x in releases){
+ var release = releases[x],
+ status = Status.get(release.status_id);
if((self.next_release && (status.identifier == 'ignored' || status.identifier == 'failed')) || (!self.next_release && status.identifier == 'available')){
- self.hide_on_click = false;
- self.show();
- buttons_done = true;
+ self.showHelper();
+ break;
}
-
- });
-
+ }
}
},
- show: function(e){
+ createReleases: function(){
var self = this;
- if(e)
- (e).preventDefault();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
self.release_container = new Element('div.releases.table').adopt(
self.trynext_container = new Element('div.buttons.try_container')
)
- ).inject(self.movie, 'top');
+ );
// Header
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);
},
+ 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){
return release.info[type] || 'n/a'
},
@@ -251,14 +307,15 @@ MA.Release = new Class({
var release_el = self.release_container.getElement('#release_'+release.id),
icon = release_el.getElement('.download.icon');
- icon.addClass('spinner');
+ self.movie.busy(true);
Api.request('release.download', {
'data': {
'id': release.id
},
'onComplete': function(json){
- icon.removeClass('spinner')
+ self.movie.busy(false);
+
if(json.success)
icon.addClass('completed');
else
@@ -281,6 +338,8 @@ MA.Release = new Class({
tryNextRelease: function(movie_id){
var self = this;
+ self.createReleases();
+
if(self.last_release)
self.ignore(self.last_release);
diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css
index 65fe5209..dd1bcbd3 100644
--- a/couchpotato/core/plugins/movie/static/movie.css
+++ b/couchpotato/core/plugins/movie/static/movie.css
@@ -39,7 +39,7 @@
overflow: hidden;
width: 100%;
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),
@@ -95,7 +95,7 @@
width: 938px;
box-shadow: none;
border: 0;
- background: none;
+ background: #4e5969;
}
.movies.thumbs_list .data {
@@ -322,8 +322,8 @@
right: 10px;
}
- .movies .data:hover .action { opacity: 0.6; }
- .movies .data:hover .action:hover { opacity: 1; }
+ .movies .movie:hover .action { opacity: 0.6; }
+ .movies .movie:hover .action:hover { opacity: 1; }
.movies.mass_edit_list .data .actions {
display: none;
}
@@ -338,8 +338,8 @@
opacity: 0;
}
- .movies.list_list .movie:not(.details_view) .data:hover .actions,
- .movies.mass_edit_list .data:hover .actions {
+ .movies.list_list .movie:not(.details_view):hover .actions,
+ .movies.mass_edit_list .movie:hover .actions {
margin: 0;
background: #4e5969;
top: 2px;
@@ -510,6 +510,29 @@
.movies .movie .releases .last_release > :first-child {
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 {
display: block;
diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py
index 4cd5b8de..c32ad81d 100644
--- a/couchpotato/core/plugins/quality/main.py
+++ b/couchpotato/core/plugins/quality/main.py
@@ -19,8 +19,8 @@ class QualityPlugin(Plugin):
qualities = [
{'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': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
+ {'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': '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': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py
index 02843f85..68bf60a6 100644
--- a/couchpotato/core/plugins/release/main.py
+++ b/couchpotato/core/plugins/release/main.py
@@ -160,7 +160,9 @@ class Release(Plugin):
db = get_session()
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()
if rel:
@@ -168,6 +170,8 @@ class Release(Plugin):
for info in rel.info:
item[info.identifier] = info.value
+ fireEvent('notify.frontend', type = 'release.download', data = True, message = 'Snatching "%s"' % item['name'])
+
# Get matching provider
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
@@ -182,8 +186,14 @@ class Release(Plugin):
}), manual = True, single = True)
if success:
- rel.status_id = status_snatched.get('id')
- db.commit()
+ db.expunge_all()
+ 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({
'success': success
diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py
index 6ce21922..22277ba5 100644
--- a/couchpotato/core/plugins/renamer/__init__.py
+++ b/couchpotato/core/plugins/renamer/__init__.py
@@ -13,7 +13,7 @@ rename_options = {
'thename': 'The Moviename',
'year': 'Year (2011)',
'first': 'First letter (M)',
- 'quality': 'Quality (720P)',
+ 'quality': 'Quality (720p)',
'video': 'Video (x264)',
'audio': 'Audio (DTS)',
'group': 'Releasegroup name',
diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py
index b7062157..aba20724 100644
--- a/couchpotato/core/plugins/renamer/main.py
+++ b/couchpotato/core/plugins/renamer/main.py
@@ -571,8 +571,16 @@ class Renamer(Plugin):
found = False
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']
log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft))
diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py
index b822bc0f..b2ac938c 100644
--- a/couchpotato/core/plugins/scanner/main.py
+++ b/couchpotato/core/plugins/scanner/main.py
@@ -11,6 +11,7 @@ from subliminal.videos import Video
import enzyme
import os
import re
+import threading
import time
import traceback
@@ -74,7 +75,7 @@ class Scanner(Plugin):
'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 = [
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
@@ -388,6 +389,11 @@ class Scanner(Plugin):
if on_found:
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:
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
else:
diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py
index b4c4937a..a34b0b47 100644
--- a/couchpotato/core/plugins/searcher/main.py
+++ b/couchpotato/core/plugins/searcher/main.py
@@ -285,10 +285,10 @@ class Searcher(Plugin):
if filedata == 'try_next':
return filedata
- successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
-
- if successful:
+ download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
+ log.debug('Downloader result: %s', download_result)
+ if download_result:
try:
# Mark release as snatched
db = get_session()
@@ -298,6 +298,15 @@ class Searcher(Plugin):
done_status = fireEvent('status.get', 'done', single = True)
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()
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
@@ -333,7 +342,7 @@ class Searcher(Plugin):
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
diff --git a/couchpotato/core/plugins/subtitle/__init__.py b/couchpotato/core/plugins/subtitle/__init__.py
index 686d385e..bbd40853 100644
--- a/couchpotato/core/plugins/subtitle/__init__.py
+++ b/couchpotato/core/plugins/subtitle/__init__.py
@@ -20,7 +20,7 @@ config = [{
},
{
'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 on Wikipedia',
},
# {
# 'name': 'automatic',
diff --git a/couchpotato/core/plugins/trailer/__init__.py b/couchpotato/core/plugins/trailer/__init__.py
index 282b3482..e4c51bd1 100644
--- a/couchpotato/core/plugins/trailer/__init__.py
+++ b/couchpotato/core/plugins/trailer/__init__.py
@@ -22,7 +22,7 @@ config = [{
'name': 'quality',
'default': '720p',
'type': 'dropdown',
- 'values': [('1080P', '1080p'), ('720P', '720p'), ('480P', '480p')],
+ 'values': [('1080p', '1080p'), ('720p', '720p'), ('480P', '480p')],
},
{
'name': 'name',
diff --git a/couchpotato/core/providers/automation/letterboxd/__init__.py b/couchpotato/core/providers/automation/letterboxd/__init__.py
new file mode 100644
index 00000000..f2b8486b
--- /dev/null
+++ b/couchpotato/core/providers/automation/letterboxd/__init__.py
@@ -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 Letterboxd 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'],
+ },
+ ],
+ },
+ ],
+}]
diff --git a/couchpotato/core/providers/automation/letterboxd/main.py b/couchpotato/core/providers/automation/letterboxd/main.py
new file mode 100644
index 00000000..7bae2ad5
--- /dev/null
+++ b/couchpotato/core/providers/automation/letterboxd/main.py
@@ -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
diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py
index 141ee4fe..6c8deb1e 100644
--- a/couchpotato/core/settings/model.py
+++ b/couchpotato/core/settings/model.py
@@ -76,7 +76,7 @@ class LibraryTitle(Entity):
title = Field(Unicode)
simple_title = Field(Unicode, index = True)
- default = Field(Boolean, index = True)
+ default = Field(Boolean, default = False, index = True)
language = OneToMany('Language')
libraries = ManyToOne('Library')
@@ -141,12 +141,12 @@ class Status(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')
identifier = Field(String(20), unique = True)
label = Field(Unicode(20))
- order = Field(Integer, index = True)
+ order = Field(Integer, default = 0, index = True)
size_min = Field(Integer)
size_max = Field(Integer)
@@ -160,21 +160,27 @@ class Profile(Entity):
using_options(order_by = 'order')
label = Field(Unicode(50))
- order = Field(Integer, index = True)
- core = Field(Boolean)
- hide = Field(Boolean)
+ order = Field(Integer, default = 0, index = True)
+ core = Field(Boolean, default = False)
+ hide = Field(Boolean, default = False)
movie = OneToMany('Movie')
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):
""""""
using_options(order_by = 'order')
- order = Field(Integer, index = True)
- finish = Field(Boolean)
- wait_for = Field(Integer)
+ order = Field(Integer, default = 0, index = True)
+ finish = Field(Boolean, default = True)
+ wait_for = Field(Integer, default = 0)
quality = ManyToOne('Quality')
profile = ManyToOne('Profile')
@@ -185,7 +191,7 @@ class File(Entity):
path = Field(Unicode(255), nullable = False, unique = True)
part = Field(Integer, default = 1)
- available = Field(Boolean)
+ available = Field(Boolean, default = True)
type = ManyToOne('FileType')
properties = OneToMany('FileProperty')
diff --git a/couchpotato/runner.py b/couchpotato/runner.py
index de538485..0ae14ce0 100644
--- a/couchpotato/runner.py
+++ b/couchpotato/runner.py
@@ -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)
current_db_version = db_version(db, repo)
- if current_db_version < latest_db_version and not development:
- log.info('Doing database upgrade. From %d to %d', (current_db_version, latest_db_version))
- upgrade(db, repo)
+ if current_db_version < latest_db_version:
+ if development:
+ 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
from couchpotato.core.settings.model import setup
diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js
index c1e91162..004f0513 100644
--- a/couchpotato/static/scripts/page/home.js
+++ b/couchpotato/static/scripts/page/home.js
@@ -23,7 +23,7 @@ Page.Home = new Class({
'identifier': 'snatched',
'load_more': false,
'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',
'on_empty_element': new Element('div'),
'filter': {
diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js
index ddb2abf1..6e563bff 100644
--- a/couchpotato/static/scripts/page/settings.js
+++ b/couchpotato/static/scripts/page/settings.js
@@ -297,7 +297,7 @@ Page.Settings = new Class({
return group_el
},
-
+
createList: function(content_container){
return new Element('div.option_list').grab(
new Element('h3', {
@@ -1283,6 +1283,7 @@ Option.Combined = new Class({
self.values = {}
self.inputs = {}
self.items = []
+ self.labels = {}
self.options.combine.each(function(name){
@@ -1302,9 +1303,10 @@ Option.Combined = new Class({
var head = new Element('div.head').inject(self.combined_list)
Object.each(self.inputs, function(input, name){
+ self.labels[name] = input.getPrevious().get('text')
new Element('abbr', {
'class': name,
- 'text': input.getPrevious().get('text'),
+ 'text': self.labels[name],
//'title': input.getNext().get('text')
}).inject(head)
})
@@ -1367,7 +1369,7 @@ Option.Combined = new Class({
value_count++;
new Element('input[type=text].inlay.'+name, {
'value': value,
- 'placeholder': name,
+ 'placeholder': self.labels[name] || name,
'events': {
'keyup': self.saveCombined.bind(self),
'change': self.saveCombined.bind(self)
diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css
index 3ad502b9..91bd1524 100644
--- a/couchpotato/static/style/main.css
+++ b/couchpotato/static/style/main.css
@@ -134,10 +134,9 @@ body > .spinner, .mask{
text-decoration: none;
font-weight: bold;
line-height: 1;
- border-radius: 5px;
- box-shadow: 0 1px 3px rgba(0,0,0,0.5);
+ border-radius: 2px;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.3);
text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
- border-bottom: 1px solid rgba(0,0,0,0.25);
cursor: pointer;
}
.button.red { background-color: #ff0000; }