Browse Source

Merge branch 'refs/heads/develop'

pull/945/head
Ruud 13 years ago
parent
commit
eb30dff986
  1. 6
      README.md
  2. 1
      couchpotato/core/_base/_core/__init__.py
  3. 3
      couchpotato/core/_base/updater/__init__.py
  4. 25
      couchpotato/core/downloaders/base.py
  5. 1
      couchpotato/core/downloaders/nzbget/__init__.py
  6. 1
      couchpotato/core/downloaders/pneumatic/__init__.py
  7. 138
      couchpotato/core/downloaders/sabnzbd/main.py
  8. 61
      couchpotato/core/event.py
  9. 8
      couchpotato/core/helpers/variable.py
  10. 2
      couchpotato/core/logger.py
  11. 3
      couchpotato/core/notifications/core/main.py
  12. 5
      couchpotato/core/plugins/automation/__init__.py
  13. 19
      couchpotato/core/plugins/base.py
  14. 25
      couchpotato/core/plugins/browser/main.py
  15. 2
      couchpotato/core/plugins/file/main.py
  16. 1
      couchpotato/core/plugins/library/main.py
  17. 6
      couchpotato/core/plugins/movie/main.py
  18. 39
      couchpotato/core/plugins/movie/static/list.js
  19. 2
      couchpotato/core/plugins/movie/static/movie.css
  20. 65
      couchpotato/core/plugins/movie/static/movie.js
  21. 2
      couchpotato/core/plugins/movie/static/search.js
  22. 10
      couchpotato/core/plugins/profile/main.py
  23. 19
      couchpotato/core/plugins/profile/static/profile.js
  24. 42
      couchpotato/core/plugins/quality/main.py
  25. 11
      couchpotato/core/plugins/release/main.py
  26. 73
      couchpotato/core/plugins/renamer/main.py
  27. 1
      couchpotato/core/plugins/scanner/main.py
  28. 2
      couchpotato/core/plugins/score/scores.py
  29. 55
      couchpotato/core/plugins/searcher/main.py
  30. 4
      couchpotato/core/plugins/subtitle/__init__.py
  31. 2
      couchpotato/core/plugins/subtitle/main.py
  32. 10
      couchpotato/core/plugins/trailer/__init__.py
  33. 25
      couchpotato/core/plugins/wizard/static/wizard.css
  34. 72
      couchpotato/core/plugins/wizard/static/wizard.js
  35. 1
      couchpotato/core/providers/movie/_modifier/main.py
  36. 1
      couchpotato/core/providers/movie/couchpotatoapi/main.py
  37. 2
      couchpotato/core/providers/nzb/mysterbin/__init__.py
  38. 2
      couchpotato/core/providers/nzb/newzbin/__init__.py
  39. 3
      couchpotato/core/providers/nzb/newznab/__init__.py
  40. 2
      couchpotato/core/providers/nzb/nzbclub/__init__.py
  41. 2
      couchpotato/core/providers/nzb/nzbindex/__init__.py
  42. 2
      couchpotato/core/providers/nzb/nzbmatrix/__init__.py
  43. 2
      couchpotato/core/providers/nzb/nzbsrus/__init__.py
  44. 3
      couchpotato/core/providers/torrent/kickasstorrents/__init__.py
  45. 8
      couchpotato/core/providers/torrent/passthepopcorn/__init__.py
  46. 2
      couchpotato/core/providers/torrent/publichd/__init__.py
  47. 2
      couchpotato/core/providers/torrent/sceneaccess/__init__.py
  48. 2
      couchpotato/core/providers/torrent/scenehd/__init__.py
  49. 9
      couchpotato/core/providers/torrent/thepiratebay/__init__.py
  50. 2
      couchpotato/core/providers/torrent/torrentleech/__init__.py
  51. 2
      couchpotato/core/settings/__init__.py
  52. 12
      couchpotato/runner.py
  53. BIN
      couchpotato/static/images/emptylist.png
  54. 41
      couchpotato/static/scripts/couchpotato.js
  55. 40
      couchpotato/static/scripts/page/settings.js
  56. 61
      couchpotato/static/scripts/page/wanted.js
  57. 12
      couchpotato/static/style/main.css
  58. 20
      couchpotato/static/style/page/settings.css

6
README.md

@ -33,7 +33,7 @@ Linux (ubuntu / debian):
* 'cd' to the folder of your choosing. * 'cd' to the folder of your choosing.
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git` * Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
* Then do `python CouchPotatoServer/CouchPotato.py` to start * Then do `python CouchPotatoServer/CouchPotato.py` to start
* To run on boot copy the init script. `cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato` * To run on boot copy the init script. `sudo cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato`
* Change the paths inside the init script. `nano /etc/init.d/couchpotato` * Change the paths inside the init script. `sudo nano /etc/init.d/couchpotato`
* Make it executable. `chmod +x /etc/init.d/couchpotato` * Make it executable. `sudo chmod +x /etc/init.d/couchpotato`
* Add it to defaults. `sudo update-rc.d couchpotato defaults` * Add it to defaults. `sudo update-rc.d couchpotato defaults`

1
couchpotato/core/_base/_core/__init__.py

@ -27,6 +27,7 @@ config = [{
'name': 'host', 'name': 'host',
'advanced': True, 'advanced': True,
'default': '0.0.0.0', 'default': '0.0.0.0',
'hidden': True,
'label': 'IP', 'label': 'IP',
'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.', 'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.',
}, },

3
couchpotato/core/_base/updater/__init__.py

@ -1,4 +1,6 @@
from .main import Updater from .main import Updater
from couchpotato.environment import Env
import os
def start(): def start():
return Updater() return Updater()
@ -33,6 +35,7 @@ config = [{
{ {
'name': 'git_command', 'name': 'git_command',
'default': 'git', 'default': 'git',
'hidden': not os.path.isdir(os.path.join(Env.get('app_dir'), '.git')),
'advanced': True 'advanced': True
}, },
], ],

25
couchpotato/core/downloaders/base.py

@ -1,10 +1,7 @@
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.encoding import toSafeString
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
import os
import random import random
import re import re
@ -23,29 +20,17 @@ class Downloader(Plugin):
def __init__(self): def __init__(self):
addEvent('download', self.download) addEvent('download', self.download)
addEvent('download.status', self.getDownloadStatus) addEvent('download.status', self.getAllDownloadStatus)
addEvent('download.remove_failed', self.removeFailed)
def download(self, data = {}, movie = {}, manual = False, filedata = None): def download(self, data = {}, movie = {}, manual = False, filedata = None):
pass pass
def getDownloadStatus(self, data = {}, movie = {}): def getAllDownloadStatus(self):
return False return False
def createNzbName(self, data, movie): def removeFailed(self, name = {}, nzo_id = {}):
tag = self.cpTag(movie) return False
return '%s%s' % (toSafeString(data.get('name')[:127 - len(tag)]), tag)
def createFileName(self, data, filedata, movie):
name = os.path.join(self.createNzbName(data, movie))
if data.get('type') == 'nzb' and 'DOCTYPE nzb' not in filedata and '</nzb>' not in filedata:
return '%s.%s' % (name, 'rar')
return '%s.%s' % (name, data.get('type'))
def cpTag(self, movie):
if Env.setting('enabled', 'renamer'):
return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else ''
return ''
def isCorrectType(self, item_type): def isCorrectType(self, item_type):
is_correct = item_type in self.type is_correct = item_type in self.type

1
couchpotato/core/downloaders/nzbget/__init__.py

@ -11,7 +11,6 @@ config = [{
'name': 'nzbget', 'name': 'nzbget',
'label': 'NZBGet', 'label': 'NZBGet',
'description': 'Send NZBs to your NZBGet installation.', 'description': 'Send NZBs to your NZBGet installation.',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',

1
couchpotato/core/downloaders/pneumatic/__init__.py

@ -12,7 +12,6 @@ config = [{
'name': 'pneumatic', 'name': 'pneumatic',
'label': 'Pneumatic', 'label': 'Pneumatic',
'description': 'Download the .strm file to a specific folder.', 'description': 'Download the .strm file to a specific folder.',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',

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

@ -1,6 +1,6 @@
from couchpotato.core.downloaders.base import Downloader from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.helpers.variable import cleanHost, mergeDicts
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
import json import json
import traceback import traceback
@ -63,106 +63,92 @@ class Sabnzbd(Downloader):
log.error("Unknown error: " + result[:40]) log.error("Unknown error: " + result[:40])
return False return False
def getDownloadStatus(self, data = {}, movie = {}): def getAllDownloadStatus(self):
if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): if self.isDisabled(manual = False):
return return False
nzbname = self.createNzbName(data, movie) log.debug('Checking SABnzbd download status.')
log.info('Checking download status of "%s" at SABnzbd.', nzbname)
# Go through Queue # Go through Queue
params = {
'apikey': self.conf('api_key'),
'mode': 'queue',
'output': 'json'
}
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
try: try:
sab = self.urlopen(url, timeout = 60, show_error = False) queue = self.call({
'mode': 'queue',
})
except: except:
log.error('Failed checking status: %s', traceback.format_exc()) log.error('Failed getting queue: %s', traceback.format_exc())
return False return False
# Go through history items
try: try:
history = json.loads(sab) history = self.call({
'mode': 'history',
'limit': 15,
})
except: except:
log.debug("Result text from SAB: " + sab[:40]) log.error('Failed getting history json: %s', traceback.format_exc())
log.error('Failed parsing json status: %s', traceback.format_exc())
return False return False
try: statuses = []
for slot in history['queue']['slots']:
log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft']))
if slot['filename'] == nzbname:
return slot['status'].lower()
except:
log.debug('No items in queue: %s', (traceback.format_exc()))
# Go through history items # Get busy releases
params = { for item in queue.get('slots', []):
'apikey': self.conf('api_key'), statuses.append({
'mode': 'history', 'id': item['nzo_id'],
'limit': 15, 'name': item['filename'],
'output': 'json' 'status': 'busy',
} 'original_status': item['status'],
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) 'timeleft': item['timeleft'] if not queue['paused'] else -1,
})
try: # Get old releases
sab = self.urlopen(url, timeout = 60, show_error = False) for item in history.get('slots', []):
except:
log.error('Failed getting history: %s', traceback.format_exc())
return
try: status = 'busy'
history = json.loads(sab) if item['status'] == 'Failed' or (item['status'] == 'Completed' and item['fail_message'].strip()):
except: status = 'failed'
log.debug("Result text from SAB: " + sab[:40]) elif item['status'] == 'Completed':
log.error('Failed parsing history json: %s', traceback.format_exc()) status = 'completed'
return
try: statuses.append({
for slot in history['history']['slots']: 'id': item['nzo_id'],
log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) 'name': item['name'],
if slot['name'] == nzbname: 'status': status,
# Note: if post process even if failed is on in SabNZBd, it will complete with a fail message 'original_status': item['status'],
if slot['status'] == 'Failed' or (slot['status'] == 'Completed' and slot['fail_message'].strip()): 'timeleft': 0,
})
# Delete failed download return statuses
if self.conf('delete_failed', default = True):
log.info('%s failed downloading, deleting...', slot['name']) def removeFailed(self, item):
params = {
'apikey': self.conf('api_key'), if not self.conf('delete_failed', default = True):
return False
log.info('%s failed downloading, deleting...', item['name'])
try:
self.call({
'mode': 'history', 'mode': 'history',
'name': 'delete', 'name': 'delete',
'del_files': '1', 'del_files': '1',
'value': slot['nzo_id'] 'value': item['id']
} }, use_json = False)
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
try:
sab = self.urlopen(url, timeout = 60, show_error = False)
except: except:
log.error('Failed deleting: %s', traceback.format_exc()) log.error('Failed deleting: %s', traceback.format_exc())
return False return False
result = sab.strip() return True
if not result:
log.error("SABnzbd didn't return anything.")
log.debug("Result text from SAB: " + result[:40]) def call(self, params, use_json = True):
if result == "ok":
log.info('SabNZBd deleted failed release %s successfully.', slot['name'])
elif result == "Missing authentication":
log.error("Incorrect username/password or API?.")
else:
log.error("Unknown error: " + result[:40])
return 'failed' url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(mergeDicts(params, {
'apikey': self.conf('api_key'),
'output': 'json'
}))
data = self.urlopen(url, timeout = 60, show_error = False)
if use_json:
return json.loads(data)[params['mode']]
else: else:
return slot['status'].lower() return data
except:
log.debug('No items in history: %s', (traceback.format_exc()))
return 'not_found'

61
couchpotato/core/event.py

@ -46,49 +46,30 @@ def fireEvent(name, *args, **kwargs):
#log.debug('Firing event %s', name) #log.debug('Firing event %s', name)
try: try:
# Fire after event options = {
is_after_event = False 'is_after_event': False, # Fire after event
'on_complete': False, # onComplete event
'single': False, # Return single handler
'merge': False, # Merge items
'in_order': False, # Fire them in specific order, waits for the other to finish
}
# Do options
for x in options:
try: try:
del kwargs['is_after_event'] val = kwargs[x]
is_after_event = True del kwargs[x]
except: pass options[x] = val
# onComplete event
on_complete = False
try:
on_complete = kwargs['on_complete']
del kwargs['on_complete']
except: pass
# Return single handler
single = False
try:
del kwargs['single']
single = True
except: pass
# Merge items
merge = False
try:
del kwargs['merge']
merge = True
except: pass
# Merge items
in_order = False
try:
del kwargs['in_order']
in_order = True
except: pass except: pass
e = events[name] e = events[name]
if not in_order: e.lock.acquire() if not options['in_order']: e.lock.acquire()
e.asynchronous = False e.asynchronous = False
e.in_order = in_order e.in_order = options['in_order']
result = e(*args, **kwargs) result = e(*args, **kwargs)
if not in_order: e.lock.release() if not options['in_order']: e.lock.release()
if single and not merge: if options['single'] and not options['merge']:
results = None results = None
# Loop over results, stop when first not None result is found. # Loop over results, stop when first not None result is found.
@ -112,7 +93,7 @@ def fireEvent(name, *args, **kwargs):
errorHandler(r[1]) errorHandler(r[1])
# Merge # Merge
if merge and len(results) > 0: if options['merge'] and len(results) > 0:
# Dict # Dict
if type(results[0]) == dict: if type(results[0]) == dict:
merged = {} merged = {}
@ -133,11 +114,11 @@ def fireEvent(name, *args, **kwargs):
log.debug('Return modified results for %s', name) log.debug('Return modified results for %s', name)
results = modified_results results = modified_results
if not is_after_event: if not options['is_after_event']:
fireEvent('%s.after' % name, is_after_event = True) fireEvent('%s.after' % name, is_after_event = True)
if on_complete: if options['on_complete']:
on_complete() options['on_complete']()
return results return results
except KeyError, e: except KeyError, e:

8
couchpotato/core/helpers/variable.py

@ -118,6 +118,14 @@ def getTitle(library_dict):
try: try:
return library_dict['titles'][0]['title'] return library_dict['titles'][0]['title']
except: except:
try:
for title in library_dict.titles:
if title.default:
return title.title
except:
log.error('Could not get title for %s', library_dict.identifier)
return None
log.error('Could not get title for %s', library_dict['identifier']) log.error('Could not get title for %s', library_dict['identifier'])
return None return None
except: except:

2
couchpotato/core/logger.py

@ -5,7 +5,7 @@ import traceback
class CPLog(object): class CPLog(object):
context = '' context = ''
replace_private = ['api', 'apikey', 'api_key', 'password', 'username', 'h'] replace_private = ['api', 'apikey', 'api_key', 'password', 'username', 'h', 'uid', 'key']
def __init__(self, context = ''): def __init__(self, context = ''):
if context.endswith('.main'): if context.endswith('.main'):

3
couchpotato/core/notifications/core/main.py

@ -79,7 +79,6 @@ class CoreNotifier(Notification):
q.update({Notif.read: True}) q.update({Notif.read: True})
db.commit() db.commit()
#db.close()
return jsonified({ return jsonified({
'success': True 'success': True
@ -107,7 +106,6 @@ class CoreNotifier(Notification):
ndict['type'] = 'notification' ndict['type'] = 'notification'
notifications.append(ndict) notifications.append(ndict)
#db.close()
return jsonified({ return jsonified({
'success': True, 'success': True,
'empty': len(notifications) == 0, 'empty': len(notifications) == 0,
@ -133,7 +131,6 @@ class CoreNotifier(Notification):
self.frontend(type = listener, data = data) self.frontend(type = listener, data = data)
#db.close()
return True return True
def frontend(self, type = 'notification', data = {}, message = None): def frontend(self, type = 'notification', data = {}, message = None):

5
couchpotato/core/plugins/automation/__init__.py

@ -5,13 +5,12 @@ def start():
config = [{ config = [{
'name': 'automation', 'name': 'automation',
'order': 30, 'order': 101,
'groups': [ 'groups': [
{ {
'tab': 'automation', 'tab': 'automation',
'name': 'automation', 'name': 'automation',
'label': 'Automation', 'label': 'Minimal movie requirements',
'description': 'Minimal movie requirements',
'options': [ 'options': [
{ {
'name': 'year', 'name': 'year',

19
couchpotato/core/plugins/base.py

@ -1,7 +1,8 @@
from StringIO import StringIO from StringIO import StringIO
from couchpotato import addView from couchpotato import addView
from couchpotato.core.event import fireEvent, addEvent from couchpotato.core.event import fireEvent, addEvent
from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss, \
toSafeString
from couchpotato.core.helpers.variable import getExt from couchpotato.core.helpers.variable import getExt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.environment import Env from couchpotato.environment import Env
@ -245,6 +246,22 @@ class Plugin(object):
Env.get('cache').set(cache_key, value, timeout) Env.get('cache').set(cache_key, value, timeout)
return value return value
def createNzbName(self, data, movie):
tag = self.cpTag(movie)
return '%s%s' % (toSafeString(data.get('name')[:127 - len(tag)]), tag)
def createFileName(self, data, filedata, movie):
name = os.path.join(self.createNzbName(data, movie))
if data.get('type') == 'nzb' and 'DOCTYPE nzb' not in filedata and '</nzb>' not in filedata:
return '%s.%s' % (name, 'rar')
return '%s.%s' % (name, data.get('type'))
def cpTag(self, movie):
if Env.setting('enabled', 'renamer'):
return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else ''
return ''
def isDisabled(self): def isDisabled(self):
return not self.isEnabled() return not self.isEnabled()

25
couchpotato/core/plugins/browser/main.py

@ -27,6 +27,8 @@ class FileBrowser(Plugin):
}, },
'return': {'type': 'object', 'example': """{ 'return': {'type': 'object', 'example': """{
'is_root': bool, //is top most folder 'is_root': bool, //is top most folder
'parent': string, //parent folder of requested path
'home': string, //user home folder
'empty': bool, //directory is empty 'empty': bool, //directory is empty
'dirs': array, //directory names 'dirs': array, //directory names
}"""} }"""}
@ -64,14 +66,35 @@ class FileBrowser(Plugin):
path = getParam('path', '/') path = getParam('path', '/')
# Set proper home dir for some systems
try:
import pwd
os.environ['HOME'] = pwd.getpwuid(os.geteuid()).pw_dir
except:
pass
home = os.path.expanduser('~')
if not path:
path = home
try: try:
dirs = self.getDirectories(path = path, show_hidden = getParam('show_hidden', True)) dirs = self.getDirectories(path = path, show_hidden = getParam('show_hidden', True))
except: except:
dirs = [] dirs = []
parent = os.path.dirname(path.rstrip(os.path.sep))
if parent == path.rstrip(os.path.sep):
parent = '/'
elif parent != '/' and parent[-2:] != ':\\':
parent += os.path.sep
return jsonified({ return jsonified({
'is_root': path == '/' or not path, 'is_root': path == '/',
'empty': len(dirs) == 0, 'empty': len(dirs) == 0,
'parent': parent,
'home': home + os.path.sep,
'platform': os.name,
'dirs': dirs, 'dirs': dirs,
}) })

2
couchpotato/core/plugins/file/main.py

@ -109,7 +109,6 @@ class FileManager(Plugin):
db.commit() db.commit()
type_dict = ft.to_dict() type_dict = ft.to_dict()
#db.close()
return type_dict return type_dict
def getTypes(self): def getTypes(self):
@ -122,5 +121,4 @@ class FileManager(Plugin):
for type_object in results: for type_object in results:
types.append(type_object.to_dict()) types.append(type_object.to_dict())
#db.close()
return types return types

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

@ -53,7 +53,6 @@ class LibraryPlugin(Plugin):
library_dict = l.to_dict(self.default_dict) library_dict = l.to_dict(self.default_dict)
#db.close()
return library_dict return library_dict
def update(self, identifier, default_title = '', force = False): def update(self, identifier, default_title = '', force = False):

6
couchpotato/core/plugins/movie/main.py

@ -431,9 +431,11 @@ class MoviePlugin(Plugin):
movie = db.query(Movie).filter_by(id = movie_id).first() movie = db.query(Movie).filter_by(id = movie_id).first()
if movie: if movie:
deleted = False
if delete_from == 'all': if delete_from == 'all':
db.delete(movie) db.delete(movie)
db.commit() db.commit()
deleted = True
else: else:
done_status = fireEvent('status.get', 'done', single = True) done_status = fireEvent('status.get', 'done', single = True)
@ -456,6 +458,7 @@ class MoviePlugin(Plugin):
if total_releases == total_deleted: if total_releases == total_deleted:
db.delete(movie) db.delete(movie)
db.commit() db.commit()
deleted = True
elif new_movie_status: elif new_movie_status:
new_status = fireEvent('status.get', new_movie_status, single = True) new_status = fireEvent('status.get', new_movie_status, single = True)
movie.profile_id = None movie.profile_id = None
@ -464,6 +467,9 @@ class MoviePlugin(Plugin):
else: else:
fireEvent('movie.restatus', movie.id, single = True) fireEvent('movie.restatus', movie.id, single = True)
if deleted:
fireEvent('notify.frontend', type = 'movie.deleted', data = movie.to_dict())
#db.close() #db.close()
return True return True

39
couchpotato/core/plugins/movie/static/list.js

@ -35,6 +35,17 @@ var MovieList = new Class({
if(options.add_new) if(options.add_new)
App.addEvent('movie.added', self.movieAdded.bind(self)) App.addEvent('movie.added', self.movieAdded.bind(self))
App.addEvent('movie.deleted', self.movieDeleted.bind(self))
},
movieDeleted: function(notification){
var self = this;
if(!self.movies_added[notification.data.id])
self.movies_added[notification.data.id].destroy();
self.checkIfEmpty();
}, },
movieAdded: function(notification){ movieAdded: function(notification){
@ -43,6 +54,8 @@ var MovieList = new Class({
if(!self.movies_added[notification.data.id]) if(!self.movies_added[notification.data.id])
self.createMovie(notification.data, 'top'); self.createMovie(notification.data, 'top');
self.checkIfEmpty();
}, },
create: function(){ create: function(){
@ -309,6 +322,8 @@ var MovieList = new Class({
erase_movies.each(function(movie){ erase_movies.each(function(movie){
self.movies.erase(movie); self.movies.erase(movie);
movie.destroy()
}); });
self.calculateSelected(); self.calculateSelected();
@ -458,6 +473,8 @@ var MovieList = new Class({
self.addMovies(json.movies, json.total); self.addMovies(json.movies, json.total);
self.load_more.set('text', 'load more movies'); self.load_more.set('text', 'load more movies');
if(self.scrollspy) self.scrollspy.start(); if(self.scrollspy) self.scrollspy.start();
self.checkIfEmpty()
} }
}); });
}, },
@ -475,6 +492,28 @@ var MovieList = new Class({
}, },
checkIfEmpty: function(){
var self = this;
var is_empty = self.movies.length == 0;
if(is_empty && self.options.on_empty_element){
self.el.grab(self.options.on_empty_element);
if(self.navigation)
self.navigation.hide();
self.empty_element = self.options.on_empty_element;
}
else if(self.empty_element){
self.empty_element.destroy();
if(self.navigation)
self.navigation.show();
}
},
toElement: function(){ toElement: function(){
return this.el; return this.el;
} }

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

@ -534,5 +534,5 @@
} }
.movies .alph_nav .more_menu > a { .movies .alph_nav .more_menu > a {
background-position: center -157px; background-position: center -158px;
} }

65
couchpotato/core/plugins/movie/static/movie.js

@ -16,14 +16,41 @@ var Movie = new Class({
self.profile = Quality.getProfile(data.profile_id) || {}; self.profile = Quality.getProfile(data.profile_id) || {};
self.parent(self, options); self.parent(self, options);
App.addEvent('movie.update.'+data.id, self.update.bind(self)); self.addEvents();
},
addEvents: function(){
var self = this;
App.addEvent('movie.update.'+self.data.id, self.update.bind(self));
['movie.busy', 'searcher.started'].each(function(listener){ ['movie.busy', 'searcher.started'].each(function(listener){
App.addEvent(listener+'.'+data.id, function(notification){ App.addEvent(listener+'.'+self.data.id, function(notification){
if(notification.data) if(notification.data)
self.busy(true) self.busy(true)
}); });
}) })
App.addEvent('searcher.ended.'+self.data.id, function(notification){
if(notification.data)
self.busy(false)
});
},
destroy: function(){
var self = this;
self.el.destroy();
delete self.list.movies_added[self.get('id')];
self.list.movies.erase(self)
self.list.checkIfEmpty();
// Remove events
App.removeEvents('movie.update.'+self.data.id);
['movie.busy', 'searcher.started'].each(function(listener){
App.removeEvents(listener+'.'+self.data.id);
})
}, },
busy: function(set_busy){ busy: function(set_busy){
@ -359,7 +386,7 @@ var ReleaseAction = new Class({
var status = Status.get(release.status_id); var status = Status.get(release.status_id);
if((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.hide_on_click = false;
self.show(); self.show();
buttons_done = true; buttons_done = true;
@ -397,19 +424,11 @@ var ReleaseAction = new Class({
var status = Status.get(release.status_id), var status = Status.get(release.status_id),
quality = Quality.getProfile(release.quality_id) || {}, quality = Quality.getProfile(release.quality_id) || {},
info = release.info; info = release.info;
release.status = status;
if( status.identifier == 'ignored' || status.identifier == 'failed'){
self.last_release = release;
}
else if(!self.next_release && status.identifier == 'available'){
self.next_release = release;
}
// Create release // Create release
new Element('div', { new Element('div', {
'class': 'item '+status.identifier + 'class': 'item '+status.identifier,
(self.next_release && self.next_release.id == release.id ? ' next_release' : '') +
(self.last_release && self.last_release.id == release.id ? ' last_release' : ''),
'id': 'release_'+release.id 'id': 'release_'+release.id
}).adopt( }).adopt(
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}), new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
@ -442,11 +461,27 @@ var ReleaseAction = new Class({
} }
}) })
).inject(self.release_container) ).inject(self.release_container)
if(status.identifier == 'ignored' || status.identifier == 'failed' || status.identifier == 'snatched'){
if(!self.last_release || (self.last_release && self.last_release.status.identifier != 'snatched' && status.identifier == 'snatched'))
self.last_release = release;
}
else if(!self.next_release && status.identifier == 'available'){
self.next_release = release;
}
}); });
if(self.last_release){
self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release');
}
if(self.next_release){
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
}
self.trynext_container.adopt( self.trynext_container.adopt(
new Element('span.or', { new Element('span.or', {
'text': 'Download' 'text': 'This movie is snatched, if anything went wrong, download'
}), }),
self.last_release ? new Element('a.button.orange', { self.last_release ? new Element('a.button.orange', {
'text': 'the same release again', 'text': 'the same release again',
@ -455,7 +490,7 @@ var ReleaseAction = new Class({
} }
}) : null, }) : null,
self.next_release && self.last_release ? new Element('span.or', { self.next_release && self.last_release ? new Element('span.or', {
'text': 'or' 'text': ','
}) : null, }) : null,
self.next_release ? [new Element('a.button.green', { self.next_release ? [new Element('a.button.green', {
'text': self.last_release ? 'another release' : 'the best release', 'text': self.last_release ? 'another release' : 'the best release',

2
couchpotato/core/plugins/movie/static/search.js

@ -339,7 +339,7 @@ Block.Search.Item = new Class({
'height': null, 'height': null,
'width': null 'width': null
}) : null, }) : null,
self.info.in_wanted ? new Element('span.in_wanted', { self.info.in_wanted && self.info.in_wanted.profile ? new Element('span.in_wanted', {
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label 'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
}) : (in_library ? new Element('span.in_library', { }) : (in_library ? new Element('span.in_library', {
'text': 'Already in library: ' + in_library.join(', ') 'text': 'Already in library: ' + in_library.join(', ')

10
couchpotato/core/plugins/profile/main.py

@ -47,7 +47,6 @@ class ProfilePlugin(Plugin):
for profile in profiles: for profile in profiles:
temp.append(profile.to_dict(self.to_dict)) temp.append(profile.to_dict(self.to_dict))
#db.close()
return temp return temp
def save(self): def save(self):
@ -84,7 +83,6 @@ class ProfilePlugin(Plugin):
profile_dict = p.to_dict(self.to_dict) profile_dict = p.to_dict(self.to_dict)
#db.close()
return jsonified({ return jsonified({
'success': True, 'success': True,
'profile': profile_dict 'profile': profile_dict
@ -95,7 +93,6 @@ class ProfilePlugin(Plugin):
db = get_session() db = get_session()
default = db.query(Profile).first() default = db.query(Profile).first()
default_dict = default.to_dict(self.to_dict) default_dict = default.to_dict(self.to_dict)
#db.close()
return default_dict return default_dict
@ -113,7 +110,6 @@ class ProfilePlugin(Plugin):
order += 1 order += 1
db.commit() db.commit()
#db.close()
return jsonified({ return jsonified({
'success': True 'success': True
@ -137,8 +133,6 @@ class ProfilePlugin(Plugin):
except Exception, e: except Exception, e:
message = log.error('Failed deleting Profile: %s', e) message = log.error('Failed deleting Profile: %s', e)
#db.close()
return jsonified({ return jsonified({
'success': success, 'success': success,
'message': message 'message': message
@ -181,10 +175,10 @@ class ProfilePlugin(Plugin):
) )
p.types.append(profile_type) p.types.append(profile_type)
db.commit()
quality_order += 1 quality_order += 1
order += 1 order += 1
#db.close() db.commit()
return True return True

19
couchpotato/core/plugins/profile/static/profile.js

@ -86,7 +86,10 @@ var Profile = new Class({
}, },
'onComplete': function(json){ 'onComplete': function(json){
if(json.success){ if(json.success){
self.data = json.profile self.data = json.profile;
self.type_container.getElement('li:first-child input[type=checkbox]')
.set('checked', true)
.getParent().addClass('checked');
} }
} }
}); });
@ -239,9 +242,17 @@ Profile.Type = new Class({
), ),
new Element('span.finish').adopt( new Element('span.finish').adopt(
self.finish = new Element('input.inlay.finish[type=checkbox]', { self.finish = new Element('input.inlay.finish[type=checkbox]', {
'checked': data.finish, 'checked': data.finish !== undefined ? data.finish : 1,
'events': { 'events': {
'change': self.fireEvent.bind(self, 'change') 'change': function(e){
if(self.el == self.el.getParent().getElement(':first-child')){
self.finish_class.check();
alert('Top quality always finishes the search')
return;
}
self.fireEvent('change');
}
} }
}) })
), ),
@ -255,7 +266,7 @@ Profile.Type = new Class({
self.el[self.data.quality_id > 0 ? 'removeClass' : 'addClass']('is_empty'); self.el[self.data.quality_id > 0 ? 'removeClass' : 'addClass']('is_empty');
new Form.Check(self.finish); self.finish_class = new Form.Check(self.finish);
}, },

42
couchpotato/core/plugins/quality/main.py

@ -9,6 +9,7 @@ from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Quality, Profile, ProfileType from couchpotato.core.settings.model import Quality, Profile, ProfileType
import os.path import os.path
import re import re
import time
log = CPLog(__name__) log = CPLog(__name__)
@ -68,7 +69,6 @@ class QualityPlugin(Plugin):
q = mergeDicts(self.getQuality(quality.identifier), quality.to_dict()) q = mergeDicts(self.getQuality(quality.identifier), quality.to_dict())
temp.append(q) temp.append(q)
#db.close()
return temp return temp
def single(self, identifier = ''): def single(self, identifier = ''):
@ -80,7 +80,6 @@ class QualityPlugin(Plugin):
if quality: if quality:
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict()) quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
#db.close()
return quality_dict return quality_dict
def getQuality(self, identifier): def getQuality(self, identifier):
@ -100,7 +99,6 @@ class QualityPlugin(Plugin):
setattr(quality, params.get('value_type'), params.get('value')) setattr(quality, params.get('value_type'), params.get('value'))
db.commit() db.commit()
#db.close()
return jsonified({ return jsonified({
'success': True 'success': True
}) })
@ -113,46 +111,48 @@ class QualityPlugin(Plugin):
for q in self.qualities: for q in self.qualities:
# Create quality # Create quality
quality = db.query(Quality).filter_by(identifier = q.get('identifier')).first() qual = db.query(Quality).filter_by(identifier = q.get('identifier')).first()
if not quality: if not qual:
log.info('Creating quality: %s', q.get('label')) log.info('Creating quality: %s', q.get('label'))
quality = Quality() qual = Quality()
db.add(quality) qual.order = order
qual.identifier = q.get('identifier')
qual.label = toUnicode(q.get('label'))
qual.size_min, qual.size_max = q.get('size')
quality.order = order db.add(qual)
quality.identifier = q.get('identifier')
quality.label = toUnicode(q.get('label'))
quality.size_min, quality.size_max = q.get('size')
# Create single quality profile # Create single quality profile
profile = db.query(Profile).filter( prof = db.query(Profile).filter(
Profile.core == True Profile.core == True
).filter( ).filter(
Profile.types.any(quality = quality) Profile.types.any(quality = qual)
).all() ).all()
if not profile: if not prof:
log.info('Creating profile: %s', q.get('label')) log.info('Creating profile: %s', q.get('label'))
profile = Profile( prof = Profile(
core = True, core = True,
label = toUnicode(quality.label), label = toUnicode(qual.label),
order = order order = order
) )
db.add(profile) db.add(prof)
profile_type = ProfileType( profile_type = ProfileType(
quality = quality, quality = qual,
profile = profile, profile = prof,
finish = True, finish = True,
order = 0 order = 0
) )
profile.types.append(profile_type) prof.types.append(profile_type)
order += 1 order += 1
db.commit() db.commit()
#db.close() time.sleep(0.3) # Wait a moment
return True return True
def guess(self, files, extra = {}): def guess(self, files, extra = {}):

11
couchpotato/core/plugins/release/main.py

@ -88,8 +88,6 @@ class Release(Plugin):
fireEvent('movie.restatus', movie.id) fireEvent('movie.restatus', movie.id)
#db.close()
return True return True
@ -108,7 +106,6 @@ class Release(Plugin):
release_id = getParam('id') release_id = getParam('id')
#db.close()
return jsonified({ return jsonified({
'success': self.delete(release_id) 'success': self.delete(release_id)
}) })
@ -152,7 +149,6 @@ class Release(Plugin):
rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id') rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id')
db.commit() db.commit()
#db.close()
return jsonified({ return jsonified({
'success': True 'success': True
}) })
@ -161,6 +157,7 @@ class Release(Plugin):
db = get_session() db = get_session()
id = getParam('id') id = getParam('id')
status_snatched = fireEvent('status.add', 'snatched', single = True)
rel = db.query(Relea).filter_by(id = id).first() rel = db.query(Relea).filter_by(id = id).first()
if rel: if rel:
@ -181,14 +178,16 @@ class Release(Plugin):
'files': {} 'files': {}
}), manual = True, single = True) }), manual = True, single = True)
#db.close() if success:
rel.status_id = status_snatched.get('id')
db.commit()
return jsonified({ return jsonified({
'success': success 'success': success
}) })
else: else:
log.error('Couldn\'t find release with id: %s', id) log.error('Couldn\'t find release with id: %s', id)
#db.close()
return jsonified({ return jsonified({
'success': False 'success': False
}) })

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

@ -20,6 +20,7 @@ log = CPLog(__name__)
class Renamer(Plugin): class Renamer(Plugin):
renaming_started = False renaming_started = False
checking_snatched = False
def __init__(self): def __init__(self):
@ -33,6 +34,7 @@ class Renamer(Plugin):
addEvent('app.load', self.scan) addEvent('app.load', self.scan)
fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every')) fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every'))
fireEvent('schedule.interval', 'renamer.check_snatched_forced', self.scan, hours = 2)
def scanView(self): def scanView(self):
@ -386,7 +388,6 @@ class Renamer(Plugin):
if self.shuttingDown(): if self.shuttingDown():
break break
#db.close()
self.renaming_started = False self.renaming_started = False
def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = ''): def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = ''):
@ -495,6 +496,11 @@ class Renamer(Plugin):
loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
def checkSnatched(self): def checkSnatched(self):
if self.checking_snatched:
log.debug('Already checking snatched')
self.checking_snatched = True
snatched_status = fireEvent('status.get', 'snatched', single = True) snatched_status = fireEvent('status.get', 'snatched', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True) ignored_status = fireEvent('status.get', 'ignored', single = True)
failed_status = fireEvent('status.get', 'failed', single = True) failed_status = fireEvent('status.get', 'failed', single = True)
@ -504,17 +510,23 @@ class Renamer(Plugin):
db = get_session() db = get_session()
rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all() rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all()
if rels:
log.debug('Checking status snatched releases...')
scan_required = False scan_required = False
if rels:
self.checking_snatched = True
log.debug('Checking status snatched releases...')
# get queue and history (once) from SABnzbd
statuses = fireEvent('download.status', merge = True)
if not statuses:
log.debug('Download status functionality is not implemented for active downloaders.')
scan_required = True
else:
try:
for rel in rels: for rel in rels:
rel_dict = rel.to_dict({'info': {}})
# Get current selected title # Get current selected title
default_title = '' default_title = getTitle(rel.movie.library)
for title in rel.movie.library.titles:
if title.default: default_title = title.title
# Check if movie has already completed and is manage tab (legacy db correction) # Check if movie has already completed and is manage tab (legacy db correction)
if rel.movie.status_id == done_status.get('id'): if rel.movie.status_id == done_status.get('id'):
@ -523,38 +535,49 @@ class Renamer(Plugin):
db.commit() db.commit()
continue continue
item = {}
for info in rel.info:
item[info.identifier] = info.value
movie_dict = fireEvent('movie.get', rel.movie_id, single = True) movie_dict = fireEvent('movie.get', rel.movie_id, single = True)
# check status # check status
downloadstatus = fireEvent('download.status', data = item, movie = movie_dict, single = True) nzbname = self.createNzbName(rel_dict['info'], movie_dict)
if not downloadstatus:
log.debug('Download status functionality is not implemented for active downloaders.') found = False
scan_required = True for item in statuses:
else: if item['name'] == nzbname:
log.debug('Download status: %s' , downloadstatus)
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))
if item['status'] == 'busy':
pass
elif item['status'] == 'failed':
fireEvent('download.remove_failed', item, single = True)
if downloadstatus == 'failed':
if self.conf('next_on_failed'): if self.conf('next_on_failed'):
fireEvent('searcher.try_next_release', movie_id = rel.movie_id) fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
else: else:
rel.status_id = failed_status.get('id') rel.status_id = failed_status.get('id')
db.commit() db.commit()
elif item['status'] == 'completed':
log.info('Download of %s failed.', item['name'])
elif downloadstatus == 'completed':
log.info('Download of %s completed!', item['name']) log.info('Download of %s completed!', item['name'])
scan_required = True scan_required = True
elif downloadstatus == 'not_found': found = True
log.info('%s not found in downloaders', item['name']) break
if not found:
log.info('%s not found in downloaders', nzbname)
rel.status_id = ignored_status.get('id') rel.status_id = ignored_status.get('id')
db.commit() db.commit()
# Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd if self.conf('next_on_failed'):
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
except:
log.error('Failed checking for release in downloader: %s', traceback.format_exc())
if scan_required: if scan_required:
fireEvent('renamer.scan') fireEvent('renamer.scan')
self.checking_snatched = False
return True

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

@ -542,7 +542,6 @@ class Scanner(Plugin):
break break
except: except:
pass pass
#db.close()
# Search based on OpenSubtitleHash # Search based on OpenSubtitleHash
if not imdb_id and not group['is_dvd']: if not imdb_id and not group['is_dvd']:

2
couchpotato/core/plugins/score/scores.py

@ -10,7 +10,7 @@ name_scores = [
# Video # Video
'x264:1', 'h264:1', 'x264:1', 'h264:1',
# Audio # Audio
'DTS:4', 'AC3:2', 'dts:4', 'ac3:2',
# Quality # Quality
'720p:10', '1080p:10', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1', 'bd50:1', 'bd25:1', '720p:10', '1080p:10', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1', 'bd50:1', 'bd25:1',
# Language / Subs # Language / Subs

55
couchpotato/core/plugins/searcher/main.py

@ -1,6 +1,6 @@
from couchpotato import get_session from couchpotato import get_session
from couchpotato.api import addApiView from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
from couchpotato.core.helpers.encoding import simplifyString, toUnicode from couchpotato.core.helpers.encoding import simplifyString, toUnicode
from couchpotato.core.helpers.request import jsonified, getParam from couchpotato.core.helpers.request import jsonified, getParam
from couchpotato.core.helpers.variable import md5, getTitle from couchpotato.core.helpers.variable import md5, getTitle
@ -36,9 +36,38 @@ class Searcher(Plugin):
}, },
}) })
addApiView('searcher.full_search', self.allMoviesView, docs = {
'desc': 'Starts a full search for all wanted movies',
})
addApiView('searcher.progress', self.getProgress, docs = {
'desc': 'Get the progress of current full search',
'return': {'type': 'object', 'example': """{
'progress': False || object, total & to_go,
}"""},
})
# Schedule cronjob # Schedule cronjob
fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
def allMoviesView(self):
in_progress = self.in_progress
if not in_progress:
fireEventAsync('searcher.all')
fireEvent('notify.frontend', type = 'searcher.started', data = True, message = 'Full search started')
else:
fireEvent('notify.frontend', type = 'searcher.already_started', data = True, message = 'Full search already in progress')
return jsonified({
'success': not in_progress
})
def getProgress(self):
return jsonified({
'progress': self.in_progress
})
def allMovies(self): def allMovies(self):
@ -54,6 +83,11 @@ class Searcher(Plugin):
Movie.status.has(identifier = 'active') Movie.status.has(identifier = 'active')
).all() ).all()
self.in_progress = {
'total': len(movies),
'to_go': len(movies),
}
for movie in movies: for movie in movies:
movie_dict = movie.to_dict({ movie_dict = movie.to_dict({
'profile': {'types': {'quality': {}}}, 'profile': {'types': {'quality': {}}},
@ -65,15 +99,17 @@ class Searcher(Plugin):
try: try:
self.single(movie_dict) self.single(movie_dict)
except IndexError: except IndexError:
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
fireEvent('library.update', movie_dict['library']['identifier'], force = True) fireEvent('library.update', movie_dict['library']['identifier'], force = True)
except: except:
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc())) log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
self.in_progress['to_go'] -= 1
# Break if CP wants to shut down # Break if CP wants to shut down
if self.shuttingDown(): if self.shuttingDown():
break break
#db.close()
self.in_progress = False self.in_progress = False
def single(self, movie): def single(self, movie):
@ -192,7 +228,6 @@ class Searcher(Plugin):
fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True) fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True)
#db.close()
return ret return ret
def download(self, data, movie, manual = False): def download(self, data, movie, manual = False):
@ -243,7 +278,6 @@ class Searcher(Plugin):
except Exception, e: except Exception, e:
log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc())) log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc()))
#db.close()
return True return True
log.info('Tried to download, but none of the downloaders are enabled') log.info('Tried to download, but none of the downloaders are enabled')
@ -254,7 +288,7 @@ class Searcher(Plugin):
imdb_results = kwargs.get('imdb_results', False) imdb_results = kwargs.get('imdb_results', False)
retention = Env.setting('retention', section = 'nzb') retention = Env.setting('retention', section = 'nzb')
if nzb.get('seeds') is None and retention < nzb.get('age', 0): if nzb.get('seeds') is None and 0 < retention < nzb.get('age', 0):
log.info('Wrong: Outside retention, age is %s, needs %s or lower: %s', (nzb['age'], retention, nzb['name'])) log.info('Wrong: Outside retention, age is %s, needs %s or lower: %s', (nzb['age'], retention, nzb['name']))
return False return False
@ -354,9 +388,11 @@ class Searcher(Plugin):
year_name = fireEvent('scanner.name_year', name, single = True) year_name = fireEvent('scanner.name_year', name, single = True)
if movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None): if movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
if size > 3000: # Assume dvdr if size > 3000: # Assume dvdr
return 'dvdr' == preferred_quality['identifier'] log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', (size))
found['dvdr'] = True
else: # Assume dvdrip else: # Assume dvdrip
return 'dvdrip' == preferred_quality['identifier'] log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', (size))
found['dvdrip'] = True
# Allow other qualities # Allow other qualities
for allowed in preferred_quality.get('allow'): for allowed in preferred_quality.get('allow'):
@ -410,6 +446,11 @@ class Searcher(Plugin):
if not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0): if not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0):
return True return True
else: else:
# For movies before 1972
if dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0:
return True
if wanted_quality in pre_releases: if wanted_quality in pre_releases:
# Prerelease 1 week before theaters # Prerelease 1 week before theaters
if dates.get('theater') - 604800 < now: if dates.get('theater') - 604800 < now:

4
couchpotato/core/plugins/subtitle/__init__.py

@ -8,9 +8,9 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'renamer', 'tab': 'renamer',
'subtab': 'subtitles',
'name': 'subtitle', 'name': 'subtitle',
'label': 'Download subtitles after rename', 'label': 'Download subtitles',
'description': 'after rename',
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',

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

@ -40,8 +40,6 @@ class Subtitle(Plugin):
# get subtitles for those files # get subtitles for those files
subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services) subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services)
#db.close()
def searchSingle(self, group): def searchSingle(self, group):
if self.isDisabled(): return if self.isDisabled(): return

10
couchpotato/core/plugins/trailer/__init__.py

@ -8,9 +8,9 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'renamer', 'tab': 'renamer',
'subtab': 'trailer',
'name': 'trailer', 'name': 'trailer',
'label': 'Download trailer after rename', 'label': 'Download trailer',
'description': 'after rename',
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@ -24,12 +24,6 @@ config = [{
'type': 'dropdown', 'type': 'dropdown',
'values': [('1080P', '1080p'), ('720P', '720p'), ('480P', '480p')], 'values': [('1080P', '1080p'), ('720P', '720p'), ('480P', '480p')],
}, },
{
'name': 'automatic',
'default': False,
'type': 'bool',
'description': 'Automaticly search & download for movies in library',
},
], ],
}, },
], ],

25
couchpotato/core/plugins/wizard/static/wizard.css

@ -14,12 +14,13 @@
.page.wizard .tab_wrapper { .page.wizard .tab_wrapper {
background: #5c697b; background: #5c697b;
padding: 18px 0; padding: 10px 0;
font-size: 23px; font-size: 18px;
position: fixed; position: fixed;
top: 0; top: 0;
margin: 0; margin: 0;
width: 100%; width: 100%;
min-width: 960px;
left: 0; left: 0;
z-index: 2; z-index: 2;
box-shadow: 0 0 50px rgba(0,0,0,0.55); box-shadow: 0 0 50px rgba(0,0,0,0.55);
@ -36,7 +37,7 @@
display: inline-block; display: inline-block;
} }
.page.wizard .tabs li a { .page.wizard .tabs li a {
padding: 20px 30px; padding: 20px 10px;
} }
.page.wizard .tab_wrapper .pointer { .page.wizard .tab_wrapper .pointer {
@ -45,7 +46,7 @@
border-top: 10px solid #5c697b; border-top: 10px solid #5c697b;
display: block; display: block;
position: absolute; position: absolute;
top: 60px; top: 44px;
} }
.page.wizard .tab_content { .page.wizard .tab_content {
@ -58,11 +59,25 @@
.page.wizard .wgroup_finish { .page.wizard .wgroup_finish {
height: 300px; height: 300px;
} }
.page.wizard .wgroup_finish h1 {
text-align: center;
}
.page.wizard .wgroup_finish .wizard_support,
.page.wizard .wgroup_finish .description {
font-size: 25px;
line-height: 120%;
margin: 20px 0;
text-align: center;
}
.page.wizard .button.green { .page.wizard .button.green {
padding: 20px; padding: 20px;
font-size: 25px; font-size: 25px;
margin: 10px 30px; margin: 10px 30px 80px;
display: block; display: block;
text-align: center; text-align: center;
} }
.page.wizard .tab_nzb_providers {
margin: 20px 0 0 0;
}

72
couchpotato/core/plugins/wizard/static/wizard.js

@ -37,21 +37,37 @@ Page.Wizard = new Class({
}, },
'downloaders': { 'downloaders': {
'title': 'What download apps are you using?', 'title': 'What download apps are you using?',
'description': 'If you don\'t have any of these listed, you have to use Blackhole. Or drop me a line, maybe I\'ll support your download app.' 'description': 'CP needs an external download app to work with. Choose one below. For more downloaders check settings after you have filled in the wizard. If your download app isn\'t in the list, use Blackhole.'
}, },
'providers': { 'providers': {
'title': 'Are you registered at any of these sites?', 'title': 'Are you registered at any of these sites?',
'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more.' 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.',
'include': ['nzb_providers', 'torrent_providers']
}, },
'renamer': { 'renamer': {
'title': 'Move & rename the movies after downloading?', 'title': 'Move & rename the movies after downloading?',
'description': '' 'description': 'The coolest part of CP is that it can move and organize your downloaded movies automagically. Check settings and you can even download trailers, subtitles and other data when it has finished downloading. It\'s awesome!'
},
'automation': {
'title': 'Easily add movies to your wanted list!',
'description': 'You can easily add movies from your favorite movie site, like IMDB, Rotten Tomatoes, Apple Trailers and more. Just install the userscript or drag the bookmarklet to your browsers bookmarks.' +
'<br />Once installed, just click the bookmarklet on a movie page and watch the magic happen ;)',
'content': function(){
return App.createUserscriptButtons().setStyles({
'background-image': "url('"+Api.createUrl('static/userscript/userscript.png')+"')"
})
}
}, },
'finish': { 'finish': {
'title': 'Finish Up', 'title': 'Finishing Up',
'description': 'Are you done? Did you fill in everything as much as possible? Yes, ok gogogo!', 'description': 'Are you done? Did you fill in everything as much as possible?' +
'<br />Be sure to check the settings to see what more CP can do!<br /><br />' +
'<div class="wizard_support">After you\'ve used CP for a while, and you like it (which of course you will), consider supporting CP. Maybe even by writing some code. <br />Or by getting a subscription at <a href="https://usenetserver.com/partners/?a_aid=couchpotato&a_bid=3f357c6f">Usenet Server</a> or <a href="http://www.newshosting.com/partners/?a_aid=couchpotato&a_bid=a0b022df">Newshosting</a>.</div>',
'content': new Element('div').adopt( 'content': new Element('div').adopt(
new Element('a.button.green', { new Element('a.button.green', {
'styles': {
'margin-top': 20
},
'text': 'I\'m ready to start the awesomeness, wow this button is big and green!', 'text': 'I\'m ready to start the awesomeness, wow this button is big and green!',
'events': { 'events': {
'click': function(e){ 'click': function(e){
@ -76,7 +92,7 @@ Page.Wizard = new Class({
) )
} }
}, },
groups: ['welcome', 'general', 'downloaders', 'searcher', 'providers', 'renamer', 'finish'], groups: ['welcome', 'general', 'downloaders', 'searcher', 'providers', 'renamer', 'automation', 'finish'],
open: function(action, params){ open: function(action, params){
var self = this; var self = this;
@ -110,7 +126,8 @@ Page.Wizard = new Class({
var form = self.el.getElement('.uniForm'); var form = self.el.getElement('.uniForm');
var tabs = self.el.getElement('.tabs'); var tabs = self.el.getElement('.tabs');
self.groups.each(function(group){ self.groups.each(function(group, nr){
if(self.headers[group]){ if(self.headers[group]){
group_container = new Element('.wgroup_'+group, { group_container = new Element('.wgroup_'+group, {
'styles': { 'styles': {
@ -120,6 +137,14 @@ Page.Wizard = new Class({
'duration': 350 'duration': 350
} }
}); });
if(self.headers[group].include){
self.headers[group].include.each(function(inc){
group_container.addClass('wgroup_'+inc);
})
}
var content = self.headers[group].content
group_container.adopt( group_container.adopt(
new Element('h1', { new Element('h1', {
'text': self.headers[group].title 'text': self.headers[group].title
@ -127,15 +152,40 @@ Page.Wizard = new Class({
self.headers[group].description ? new Element('span.description', { self.headers[group].description ? new Element('span.description', {
'html': self.headers[group].description 'html': self.headers[group].description
}) : null, }) : null,
self.headers[group].content ? self.headers[group].content : null content ? (typeOf(content) == 'function' ? content() : content) : null
).inject(form); ).inject(form);
} }
var tab_navigation = tabs.getElement('.t_'+group); var tab_navigation = tabs.getElement('.t_'+group);
if(!tab_navigation && self.headers[group] && self.headers[group].include){
tab_navigation = []
self.headers[group].include.each(function(inc){
tab_navigation.include(tabs.getElement('.t_'+inc));
})
}
if(tab_navigation && group_container){ if(tab_navigation && group_container){
tab_navigation.inject(tabs); // Tab navigation tabs.adopt(tab_navigation); // Tab navigation
if(self.headers[group] && self.headers[group].include){
self.headers[group].include.each(function(inc){
self.el.getElement('.tab_'+inc).inject(group_container);
})
new Element('li.t_'+group).adopt(
new Element('a', {
'href': App.createUrl('wizard/'+group),
'text': (self.headers[group].label || group).capitalize()
})
).inject(tabs);
}
else
self.el.getElement('.tab_'+group).inject(group_container); // Tab content self.el.getElement('.tab_'+group).inject(group_container); // Tab content
if(self.headers[group]){
if(tab_navigation.getElement && self.headers[group]){
var a = tab_navigation.getElement('a'); var a = tab_navigation.getElement('a');
a.set('text', (self.headers[group].label || group).capitalize()); a.set('text', (self.headers[group].label || group).capitalize());
var url_split = a.get('href').split('wizard')[1].split('/'); var url_split = a.get('href').split('wizard')[1].split('/');
@ -163,6 +213,8 @@ Page.Wizard = new Class({
// Hide retention // Hide retention
self.el.getElement('.tab_searcher').hide(); self.el.getElement('.tab_searcher').hide();
self.el.getElement('.t_searcher').hide(); self.el.getElement('.t_searcher').hide();
self.el.getElement('.t_nzb_providers').hide();
self.el.getElement('.t_torrent_providers').hide();
// Add pointer // Add pointer
new Element('.tab_wrapper').wraps(tabs).adopt( new Element('.tab_wrapper').wraps(tabs).adopt(

1
couchpotato/core/providers/movie/_modifier/main.py

@ -70,7 +70,6 @@ class MovieResultModifier(Plugin):
except: except:
log.error('Tried getting more info on searched movies: %s', traceback.format_exc()) log.error('Tried getting more info on searched movies: %s', traceback.format_exc())
#db.close()
return temp return temp
def checkLibrary(self, result): def checkLibrary(self, result):

1
couchpotato/core/providers/movie/couchpotatoapi/main.py

@ -94,7 +94,6 @@ class CouchPotatoApi(MovieProvider):
db = get_session() db = get_session()
active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all() active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all()
movies = [x.library.identifier for x in active_movies] movies = [x.library.identifier for x in active_movies]
#db.close()
suggestions = self.suggest(movies, ignore) suggestions = self.suggest(movies, ignore)

2
couchpotato/core/providers/nzb/mysterbin/__init__.py

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'nzb_providers',
'name': 'Mysterbin', 'name': 'Mysterbin',
'description': 'Free provider, less accurate. See <a href="http://www.mysterbin.com/">Mysterbin</a>', 'description': 'Free provider, less accurate. See <a href="http://www.mysterbin.com/">Mysterbin</a>',
'options': [ 'options': [

2
couchpotato/core/providers/nzb/newzbin/__init__.py

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'nzb_providers',
'name': 'newzbin', 'name': 'newzbin',
'description': 'See <a href="https://www.newzbin2.es/">Newzbin</a>', 'description': 'See <a href="https://www.newzbin2.es/">Newzbin</a>',
'wizard': True, 'wizard': True,

3
couchpotato/core/providers/nzb/newznab/__init__.py

@ -8,8 +8,9 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'nzb_providers',
'name': 'newznab', 'name': 'newznab',
'order': 10,
'description': 'Enable multiple NewzNab providers such as <a href="http://nzb.su" target="_blank">NZB.su</a> and <a href="http://nzbs.org" target="_blank">nzbs.org</a>', 'description': 'Enable multiple NewzNab providers such as <a href="http://nzb.su" target="_blank">NZB.su</a> and <a href="http://nzbs.org" target="_blank">nzbs.org</a>',
'wizard': True, 'wizard': True,
'options': [ 'options': [

2
couchpotato/core/providers/nzb/nzbclub/__init__.py

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'nzb_providers',
'name': 'NZBClub', 'name': 'NZBClub',
'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>', 'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>',
'options': [ 'options': [

2
couchpotato/core/providers/nzb/nzbindex/__init__.py

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'nzb_providers',
'name': 'nzbindex', 'name': 'nzbindex',
'description': 'Free provider, less accurate. See <a href="http://www.nzbindex.nl/">NZBIndex</a>', 'description': 'Free provider, less accurate. See <a href="http://www.nzbindex.nl/">NZBIndex</a>',
'options': [ 'options': [

2
couchpotato/core/providers/nzb/nzbmatrix/__init__.py

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'nzb_providers',
'name': 'nzbmatrix', 'name': 'nzbmatrix',
'label': 'NZBMatrix', 'label': 'NZBMatrix',
'description': 'See <a href="https://nzbmatrix.com/">NZBMatrix</a>', 'description': 'See <a href="https://nzbmatrix.com/">NZBMatrix</a>',

2
couchpotato/core/providers/nzb/nzbsrus/__init__.py

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'nzb_providers',
'name': 'nzbsrus', 'name': 'nzbsrus',
'label': 'Nzbsrus', 'label': 'Nzbsrus',
'description': 'See <a href="https://www.nzbsrus.com/">NZBsRus</a>', 'description': 'See <a href="https://www.nzbsrus.com/">NZBsRus</a>',

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

@ -8,9 +8,10 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'torrent_providers',
'name': 'KickAssTorrents', 'name': 'KickAssTorrents',
'description': 'See <a href="https://kat.ph/">KickAssTorrents</a>', 'description': 'See <a href="https://kat.ph/">KickAssTorrents</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',

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

@ -5,9 +5,10 @@ def start():
config = [{ config = [{
'name': 'passthepopcorn', 'name': 'passthepopcorn',
'groups': [{ 'groups': [
{
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'torrent_providers',
'name': 'PassThePopcorn', 'name': 'PassThePopcorn',
'description': 'See <a href="http://passthepopcorn.me">PassThePopcorn.me</a>', 'description': 'See <a href="http://passthepopcorn.me">PassThePopcorn.me</a>',
'options': [ 'options': [
@ -32,5 +33,6 @@ config = [{
'type': 'password', 'type': 'password',
} }
], ],
}] }
]
}] }]

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

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'torrent_providers',
'name': 'PublicHD', 'name': 'PublicHD',
'description': 'Public Torrent site with only HD content. See <a href="http://publichd.eu/">PublicHD</a>', 'description': 'Public Torrent site with only HD content. See <a href="http://publichd.eu/">PublicHD</a>',
'options': [ 'options': [

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

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'torrent_providers',
'name': 'SceneAccess', 'name': 'SceneAccess',
'description': 'See <a href="https://sceneaccess.eu/">SceneAccess</a>', 'description': 'See <a href="https://sceneaccess.eu/">SceneAccess</a>',
'options': [ 'options': [

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

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'torrent_providers',
'name': 'SceneHD', 'name': 'SceneHD',
'description': 'See <a href="http://scenehd.org">SceneHD</a>', 'description': 'See <a href="http://scenehd.org">SceneHD</a>',
'options': [ 'options': [

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

@ -5,11 +5,13 @@ def start():
config = [{ config = [{
'name': 'thepiratebay', 'name': 'thepiratebay',
'groups': [{ 'groups': [
{
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'torrent_providers',
'name': 'ThePirateBay', 'name': 'ThePirateBay',
'description': 'The world\'s largest bittorrent tracker. See <a href="http://fucktimkuik.org/">ThePirateBay</a>', 'description': 'The world\'s largest bittorrent tracker. See <a href="http://fucktimkuik.org/">ThePirateBay</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@ -23,5 +25,6 @@ config = [{
'description': 'Domain for requests, keep empty to let CouchPotato pick.', 'description': 'Domain for requests, keep empty to let CouchPotato pick.',
} }
], ],
}] }
]
}] }]

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

@ -8,7 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'providers', 'subtab': 'torrent_providers',
'name': 'TorrentLeech', 'name': 'TorrentLeech',
'description': 'See <a href="http://torrentleech.org">TorrentLeech</a>', 'description': 'See <a href="http://torrentleech.org">TorrentLeech</a>',
'options': [ 'options': [

2
couchpotato/core/settings/__init__.py

@ -204,7 +204,6 @@ class Settings(object):
except: except:
pass pass
#db.close()
return prop return prop
def setProperty(self, identifier, value = ''): def setProperty(self, identifier, value = ''):
@ -221,4 +220,3 @@ class Settings(object):
p.value = toUnicode(value) p.value = toUnicode(value)
db.commit() db.commit()
#db.close()

12
couchpotato/runner.py

@ -170,18 +170,17 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
log.warning('%s %s %s line:%s', (category, message, filename, lineno)) log.warning('%s %s %s line:%s', (category, message, filename, lineno))
warnings.showwarning = customwarn warnings.showwarning = customwarn
# Check if database exists
db = Env.get('db_path')
db_exists = os.path.isfile(db_path)
# Load configs & plugins # Load configs & plugins
loader = Env.get('loader') loader = Env.get('loader')
loader.preload(root = base_path) loader.preload(root = base_path)
loader.run() loader.run()
# Load migrations # Load migrations
initialize = True if db_exists:
db = Env.get('db_path')
if os.path.isfile(db_path):
initialize = False
from migrate.versioning.api import version_control, db_version, version, upgrade from migrate.versioning.api import version_control, db_version, version, upgrade
repo = os.path.join(base_path, 'couchpotato', 'core', 'migration') repo = os.path.join(base_path, 'couchpotato', 'core', 'migration')
@ -201,7 +200,8 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
from couchpotato.core.settings.model import setup from couchpotato.core.settings.model import setup
setup() setup()
if initialize: # Fill database with needed stuff
if not db_exists:
fireEvent('app.initialize', in_order = True) fireEvent('app.initialize', in_order = True)
# Create app # Create app

BIN
couchpotato/static/images/emptylist.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

41
couchpotato/static/scripts/couchpotato.js

@ -281,6 +281,47 @@ var CouchPotato = new Class({
window.open(url); window.open(url);
else else
window.location = url; window.location = url;
},
createUserscriptButtons: function(){
var userscript = false;
try {
if(Components.interfaces.gmIGreasemonkeyService)
userscript = true
}
catch(e){
userscript = Browser.chrome === true;
}
var host_url = window.location.protocol + '//' + window.location.host;
return new Element('div.group_userscript').adopt(
(userscript ? [new Element('a.userscript.button', {
'text': 'Install userscript',
'href': Api.createUrl('userscript.get')+randomString()+'/couchpotato.user.js',
'target': '_self'
}), new Element('span.or[text=or]')] : null),
new Element('span.bookmarklet').adopt(
new Element('a.button.orange', {
'text': '+CouchPotato',
'href': "javascript:void((function(){var e=document.createElement('script');e.setAttribute('type','text/javascript');e.setAttribute('charset','UTF-8');e.setAttribute('src','" +
host_url + Api.createUrl('userscript.bookmark') +
"?host="+ encodeURI(host_url + Api.createUrl('userscript.get')+randomString()+'/') +
"&r='+Math.random()*99999999);document.body.appendChild(e)})());",
'target': '',
'events': {
'click': function(e){
(e).stop()
alert('Drag it to your bookmark ;)')
}
}
}),
new Element('span', {
'text': '⇽ Drag this to your bookmarks'
})
)
);
} }
}); });

40
couchpotato/static/scripts/page/settings.js

@ -211,7 +211,7 @@ Page.Settings = new Class({
if(self.tabs[tab_name] && self.tabs[tab_name].tab) if(self.tabs[tab_name] && self.tabs[tab_name].tab)
return self.tabs[tab_name].tab return self.tabs[tab_name].tab
var label = (tab.label || tab.name || tab_name).capitalize() var label = tab.label || (tab.name || tab_name).capitalize()
var tab_el = new Element('li.t_'+tab_name).adopt( var tab_el = new Element('li.t_'+tab_name).adopt(
new Element('a', { new Element('a', {
'href': App.createUrl(self.name+'/'+tab_name), 'href': App.createUrl(self.name+'/'+tab_name),
@ -244,7 +244,7 @@ Page.Settings = new Class({
if(!parent_tab.subtabs_el) if(!parent_tab.subtabs_el)
parent_tab.subtabs_el = new Element('ul.subtabs').inject(parent_tab.tab); parent_tab.subtabs_el = new Element('ul.subtabs').inject(parent_tab.tab);
var label = (tab.label || tab.name || tab_name).capitalize() var label = tab.label || (tab.name || tab_name.replace('_', ' ')).capitalize()
var tab_el = new Element('li.t_'+tab_name).adopt( var tab_el = new Element('li.t_'+tab_name).adopt(
new Element('a', { new Element('a', {
'href': App.createUrl(self.name+'/'+parent_tab_name+'/'+tab_name), 'href': App.createUrl(self.name+'/'+parent_tab_name+'/'+tab_name),
@ -274,7 +274,7 @@ Page.Settings = new Class({
'class': (group.advanced ? 'inlineLabels advanced' : 'inlineLabels') + ' group_' + (group.name || '') + ' subtab_' + (group.subtab || '') 'class': (group.advanced ? 'inlineLabels advanced' : 'inlineLabels') + ' group_' + (group.name || '') + ' subtab_' + (group.subtab || '')
}).adopt( }).adopt(
new Element('h2', { new Element('h2', {
'text': (group.label || group.name).capitalize() 'text': group.label || (group.name).capitalize()
}).adopt( }).adopt(
new Element('span.hint', { new Element('span.hint', {
'html': group.description || '' 'html': group.description || ''
@ -635,7 +635,7 @@ Option.Directory = new Class({
), ),
self.dir_list = new Element('ul', { self.dir_list = new Element('ul', {
'events': { 'events': {
'click:relay(li)': function(e, el){ 'click:relay(li:not(.empty))': function(e, el){
(e).preventDefault(); (e).preventDefault();
self.selectDirectory(el.get('data-value')) self.selectDirectory(el.get('data-value'))
}, },
@ -701,12 +701,24 @@ Option.Directory = new Class({
fillBrowser: function(json){ fillBrowser: function(json){
var self = this; var self = this;
var v = self.input.get('text'); self.data = json;
var v = self.getValue();
var previous_dir = self.getParentDir(); var previous_dir = self.getParentDir();
if(v == '')
self.input.set('text', json.home);
if(previous_dir != v && previous_dir.length >= 1 && !json.is_root){ if(previous_dir != v && previous_dir.length >= 1 && !json.is_root){
var prev_dirname = self.getCurrentDirname(previous_dir);
if(previous_dir == json.home)
prev_dirname = 'Home';
else if (previous_dir == '/' && json.platform == 'nt')
prev_dirname = 'Computer';
self.back_button.set('data-value', previous_dir) self.back_button.set('data-value', previous_dir)
self.back_button.set('html', '&laquo; '+self.getCurrentDirname(previous_dir)) self.back_button.set('html', '&laquo; '+prev_dirname)
self.back_button.show() self.back_button.show()
} }
else { else {
@ -719,23 +731,24 @@ Option.Directory = new Class({
else else
self.cached[v] = json; self.cached[v] = json;
setTimeout(function(){
self.dir_list.empty(); self.dir_list.empty();
if(json.dirs.length > 0)
json.dirs.each(function(dir){ json.dirs.each(function(dir){
if(dir.indexOf(v) != -1){
new Element('li', { new Element('li', {
'data-value': dir, 'data-value': dir,
'text': self.getCurrentDirname(dir) 'text': self.getCurrentDirname(dir)
}).inject(self.dir_list) }).inject(self.dir_list)
}
}); });
}, 50); else
new Element('li.empty', {
'text': 'Selected folder is empty'
}).inject(self.dir_list)
}, },
getDirs: function(){ getDirs: function(){
var self = this; var self = this;
var c = self.input.get('text'); var c = self.getValue();
if(self.cached[c] && self.use_cache){ if(self.cached[c] && self.use_cache){
self.fillBrowser() self.fillBrowser()
@ -754,7 +767,10 @@ Option.Directory = new Class({
getParentDir: function(dir){ getParentDir: function(dir){
var self = this; var self = this;
var v = dir || self.input.get('text'); if(!dir && self.data && self.data.parent)
return self.data.parent;
var v = dir || self.getValue();
var sep = Api.getOption('path_sep'); var sep = Api.getOption('path_sep');
var dirs = v.split(sep); var dirs = v.split(sep);
if(dirs.pop() == '') if(dirs.pop() == '')

61
couchpotato/static/scripts/page/wanted.js

@ -10,15 +10,72 @@ Page.Wanted = new Class({
if(!self.wanted){ if(!self.wanted){
self.manual_search = new Element('a', {
'title': 'Force a search for the full wanted list',
'text': 'Search all wanted',
'events':{
'click': self.doFullSearch.bind(self, true)
}
});
// See if userscript can be installed
// Wanted movies // Wanted movies
self.wanted = new MovieList({ self.wanted = new MovieList({
'identifier': 'wanted', 'identifier': 'wanted',
'status': 'active', 'status': 'active',
'actions': MovieActions, 'actions': MovieActions,
'add_new': true 'add_new': true,
'menu': [self.manual_search],
'on_empty_element': App.createUserscriptButtons().setStyles({
'background-image': "url('"+Api.createUrl('static/images/emptylist.png')+"')",
'height': 750,
'width': 800,
'padding-top': 260,
'margin-top': -50
})
}); });
$(self.wanted).inject(self.el); $(self.wanted).inject(self.el);
// Check if search is in progress
self.startProgressInterval();
}
},
doFullSearch: function(full){
var self = this;
if(!self.search_in_progress){
Api.request('searcher.full_search');
self.startProgressInterval();
}
},
startProgressInterval: function(){
var self = this;
var start_text = self.manual_search.get('text');
self.progress_interval = setInterval(function(){
Api.request('searcher.progress', {
'onComplete': function(json){
self.search_in_progress = true;
if(!json.progress){
clearInterval(self.progress_interval);
self.search_in_progress = false;
self.manual_search.set('text', start_text);
} }
else {
var progress = json.progress;
self.manual_search.set('text', 'Searching.. (' + (((progress.total-progress.to_go)/progress.total)*100).round() + '%)');
}
}
})
}, 1000);
} }
@ -222,7 +279,7 @@ window.addEvent('domready', function(){
movie.set('tween', { movie.set('tween', {
'duration': 300, 'duration': 300,
'onComplete': function(){ 'onComplete': function(){
movie.destroy(); self.movie.destroy()
} }
}); });
movie.tween('height', 0); movie.tween('height', 0);

12
couchpotato/static/style/main.css

@ -376,22 +376,12 @@ body > .spinner, .mask{
font-weight: bold; font-weight: bold;
} }
.select .list:before {
content: ' ';
height: 0;
position: absolute;
width: 0;
border: 6px solid transparent;
border-bottom-color: #282d34;
margin: -11px 0 0 70px;
}
.select .list { .select .list {
display: none; display: none;
background: #282d34; background: #282d34;
border: 1px solid #1f242b; border: 1px solid #1f242b;
position: absolute; position: absolute;
margin: 30px 0 0 0; margin: 28px 0 0 0;
box-shadow: 0 20px 20px -10px rgba(0,0,0,0.4); box-shadow: 0 20px 20px -10px rgba(0,0,0,0.4);
border-radius:3px; border-radius:3px;
z-index: 3; z-index: 3;

20
couchpotato/static/style/page/settings.css

@ -236,6 +236,19 @@
background-color: #515c68; background-color: #515c68;
} }
.page .directory_list li.empty {
background: none;
height: 100px;
text-align: center;
font-style: italic;
border: none;
line-height: 100px;
cursor: default;
color: #BBB;
text-shadow: none;
font-size: 12px;
}
.page .directory_list .actions { .page .directory_list .actions {
clear: both; clear: both;
padding: 4% 4% 2%; padding: 4% 4% 2%;
@ -319,12 +332,11 @@
.page .tag_input > ul { .page .tag_input > ul {
list-style: none; list-style: none;
padding: 3px 0;
border-radius: 3px; border-radius: 3px;
cursor: text; cursor: text;
width: 30%; width: 30%;
margin: 0 !important; margin: 0 !important;
height: 27px; min-height: 27px;
line-height: 0; line-height: 0;
display: inline-block; display: inline-block;
} }
@ -339,7 +351,7 @@
min-width: 2px; min-width: 2px;
font-size: 12px; font-size: 12px;
padding: 0; padding: 0;
margin: 0 !important; margin: 4px 0 0 !important;
border-width: 0; border-width: 0;
background: 0; background: 0;
line-height: 20px; line-height: 20px;
@ -400,7 +412,7 @@
margin: -9px 0 0 -16px; margin: -9px 0 0 -16px;
border-radius: 30px 30px 0 0; border-radius: 30px 30px 0 0;
cursor: pointer; cursor: pointer;
background: url('../../images/icon.delete.png') no-repeat center 2px, -webkit-linear-gradient( background: url('../../images/icon.delete.png') no-repeat center 2px, linear-gradient(
270deg, 270deg,
#5b9bd1 0%, #5b9bd1 0%,
#5b9bd1 100% #5b9bd1 100%

Loading…
Cancel
Save