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. 158
      couchpotato/core/downloaders/sabnzbd/main.py
  8. 65
      couchpotato/core/event.py
  9. 12
      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. 44
      couchpotato/core/plugins/quality/main.py
  25. 11
      couchpotato/core/plugins/release/main.py
  26. 111
      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. 74
      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. 58
      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. 41
      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. 50
      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.
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
* Then do `python CouchPotatoServer/CouchPotato.py` to start
* To run on boot copy the init script. `cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato`
* Change the paths inside the init script. `nano /etc/init.d/couchpotato`
* Make it executable. `chmod +x /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. `sudo nano /etc/init.d/couchpotato`
* Make it executable. `sudo chmod +x /etc/init.d/couchpotato`
* Add it to defaults. `sudo update-rc.d couchpotato defaults`

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

@ -27,6 +27,7 @@ config = [{
'name': 'host',
'advanced': True,
'default': '0.0.0.0',
'hidden': True,
'label': 'IP',
'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 couchpotato.environment import Env
import os
def start():
return Updater()
@ -33,6 +35,7 @@ config = [{
{
'name': 'git_command',
'default': 'git',
'hidden': not os.path.isdir(os.path.join(Env.get('app_dir'), '.git')),
'advanced': True
},
],

25
couchpotato/core/downloaders/base.py

@ -1,10 +1,7 @@
from base64 import b32decode, b16encode
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import toSafeString
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
import os
import random
import re
@ -23,29 +20,17 @@ class Downloader(Plugin):
def __init__(self):
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):
pass
def getDownloadStatus(self, data = {}, movie = {}):
def getAllDownloadStatus(self):
return False
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 removeFailed(self, name = {}, nzo_id = {}):
return False
def isCorrectType(self, item_type):
is_correct = item_type in self.type

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

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

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

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

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

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

65
couchpotato/core/event.py

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

12
couchpotato/core/helpers/variable.py

@ -118,8 +118,16 @@ def getTitle(library_dict):
try:
return library_dict['titles'][0]['title']
except:
log.error('Could not get title for %s', library_dict['identifier'])
return None
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'])
return None
except:
log.error('Could not get title for library item: %s', library_dict)
return None

2
couchpotato/core/logger.py

@ -5,7 +5,7 @@ import traceback
class CPLog(object):
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 = ''):
if context.endswith('.main'):

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

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

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

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

19
couchpotato/core/plugins/base.py

@ -1,7 +1,8 @@
from StringIO import StringIO
from couchpotato import addView
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.logger import CPLog
from couchpotato.environment import Env
@ -245,6 +246,22 @@ class Plugin(object):
Env.get('cache').set(cache_key, value, timeout)
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):
return not self.isEnabled()

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

@ -27,6 +27,8 @@ class FileBrowser(Plugin):
},
'return': {'type': 'object', 'example': """{
'is_root': bool, //is top most folder
'parent': string, //parent folder of requested path
'home': string, //user home folder
'empty': bool, //directory is empty
'dirs': array, //directory names
}"""}
@ -64,14 +66,35 @@ class FileBrowser(Plugin):
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:
dirs = self.getDirectories(path = path, show_hidden = getParam('show_hidden', True))
except:
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({
'is_root': path == '/' or not path,
'is_root': path == '/',
'empty': len(dirs) == 0,
'parent': parent,
'home': home + os.path.sep,
'platform': os.name,
'dirs': dirs,
})

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

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

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

@ -53,7 +53,6 @@ class LibraryPlugin(Plugin):
library_dict = l.to_dict(self.default_dict)
#db.close()
return library_dict
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()
if movie:
deleted = False
if delete_from == 'all':
db.delete(movie)
db.commit()
deleted = True
else:
done_status = fireEvent('status.get', 'done', single = True)
@ -456,6 +458,7 @@ class MoviePlugin(Plugin):
if total_releases == total_deleted:
db.delete(movie)
db.commit()
deleted = True
elif new_movie_status:
new_status = fireEvent('status.get', new_movie_status, single = True)
movie.profile_id = None
@ -464,6 +467,9 @@ class MoviePlugin(Plugin):
else:
fireEvent('movie.restatus', movie.id, single = True)
if deleted:
fireEvent('notify.frontend', type = 'movie.deleted', data = movie.to_dict())
#db.close()
return True

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

@ -35,6 +35,17 @@ var MovieList = new Class({
if(options.add_new)
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){
@ -43,6 +54,8 @@ var MovieList = new Class({
if(!self.movies_added[notification.data.id])
self.createMovie(notification.data, 'top');
self.checkIfEmpty();
},
create: function(){
@ -309,6 +322,8 @@ var MovieList = new Class({
erase_movies.each(function(movie){
self.movies.erase(movie);
movie.destroy()
});
self.calculateSelected();
@ -458,6 +473,8 @@ var MovieList = new Class({
self.addMovies(json.movies, json.total);
self.load_more.set('text', 'load more movies');
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(){
return this.el;
}

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

@ -534,5 +534,5 @@
}
.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.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){
App.addEvent(listener+'.'+data.id, function(notification){
App.addEvent(listener+'.'+self.data.id, function(notification){
if(notification.data)
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){
@ -359,7 +386,7 @@ var ReleaseAction = new Class({
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.show();
buttons_done = true;
@ -397,19 +424,11 @@ var ReleaseAction = new Class({
var status = Status.get(release.status_id),
quality = Quality.getProfile(release.quality_id) || {},
info = release.info;
if( status.identifier == 'ignored' || status.identifier == 'failed'){
self.last_release = release;
}
else if(!self.next_release && status.identifier == 'available'){
self.next_release = release;
}
release.status = status;
// Create release
new Element('div', {
'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' : ''),
'class': 'item '+status.identifier,
'id': 'release_'+release.id
}).adopt(
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)
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(
new Element('span.or', {
'text': 'Download'
'text': 'This movie is snatched, if anything went wrong, download'
}),
self.last_release ? new Element('a.button.orange', {
'text': 'the same release again',
@ -455,7 +490,7 @@ var ReleaseAction = new Class({
}
}) : null,
self.next_release && self.last_release ? new Element('span.or', {
'text': 'or'
'text': ','
}) : null,
self.next_release ? [new Element('a.button.green', {
'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,
'width': 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
}) : (in_library ? new Element('span.in_library', {
'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:
temp.append(profile.to_dict(self.to_dict))
#db.close()
return temp
def save(self):
@ -84,7 +83,6 @@ class ProfilePlugin(Plugin):
profile_dict = p.to_dict(self.to_dict)
#db.close()
return jsonified({
'success': True,
'profile': profile_dict
@ -95,7 +93,6 @@ class ProfilePlugin(Plugin):
db = get_session()
default = db.query(Profile).first()
default_dict = default.to_dict(self.to_dict)
#db.close()
return default_dict
@ -113,7 +110,6 @@ class ProfilePlugin(Plugin):
order += 1
db.commit()
#db.close()
return jsonified({
'success': True
@ -137,8 +133,6 @@ class ProfilePlugin(Plugin):
except Exception, e:
message = log.error('Failed deleting Profile: %s', e)
#db.close()
return jsonified({
'success': success,
'message': message
@ -181,10 +175,10 @@ class ProfilePlugin(Plugin):
)
p.types.append(profile_type)
db.commit()
quality_order += 1
order += 1
#db.close()
db.commit()
return True

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

@ -86,7 +86,10 @@ var Profile = new Class({
},
'onComplete': function(json){
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(
self.finish = new Element('input.inlay.finish[type=checkbox]', {
'checked': data.finish,
'checked': data.finish !== undefined ? data.finish : 1,
'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');
new Form.Check(self.finish);
self.finish_class = new Form.Check(self.finish);
},

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

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

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

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

@ -20,6 +20,7 @@ log = CPLog(__name__)
class Renamer(Plugin):
renaming_started = False
checking_snatched = False
def __init__(self):
@ -33,6 +34,7 @@ class Renamer(Plugin):
addEvent('app.load', self.scan)
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):
@ -386,7 +388,6 @@ class Renamer(Plugin):
if self.shuttingDown():
break
#db.close()
self.renaming_started = False
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()))
def checkSnatched(self):
if self.checking_snatched:
log.debug('Already checking snatched')
self.checking_snatched = True
snatched_status = fireEvent('status.get', 'snatched', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True)
failed_status = fireEvent('status.get', 'failed', single = True)
@ -504,57 +510,74 @@ class Renamer(Plugin):
db = get_session()
rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all()
if rels:
log.debug('Checking status snatched releases...')
scan_required = False
for rel in rels:
# Get current selected title
default_title = ''
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)
if rel.movie.status_id == done_status.get('id'):
log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title)
rel.status_id = ignored_status.get('id')
db.commit()
continue
item = {}
for info in rel.info:
item[info.identifier] = info.value
movie_dict = fireEvent('movie.get', rel.movie_id, single = True)
# check status
downloadstatus = fireEvent('download.status', data = item, movie = movie_dict, single = True)
if not downloadstatus:
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:
log.debug('Download status: %s' , downloadstatus)
if downloadstatus == 'failed':
if self.conf('next_on_failed'):
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
else:
rel.status_id = failed_status.get('id')
db.commit()
try:
for rel in rels:
rel_dict = rel.to_dict({'info': {}})
# Get current selected title
default_title = getTitle(rel.movie.library)
# Check if movie has already completed and is manage tab (legacy db correction)
if rel.movie.status_id == done_status.get('id'):
log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title)
rel.status_id = ignored_status.get('id')
db.commit()
continue
movie_dict = fireEvent('movie.get', rel.movie_id, single = True)
# check status
nzbname = self.createNzbName(rel_dict['info'], movie_dict)
found = False
for item in statuses:
if item['name'] == nzbname:
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 self.conf('next_on_failed'):
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
else:
rel.status_id = failed_status.get('id')
db.commit()
elif item['status'] == 'completed':
log.info('Download of %s completed!', item['name'])
scan_required = True
found = True
break
log.info('Download of %s failed.', item['name'])
if not found:
log.info('%s not found in downloaders', nzbname)
rel.status_id = ignored_status.get('id')
db.commit()
elif downloadstatus == 'completed':
log.info('Download of %s completed!', item['name'])
scan_required = True
if self.conf('next_on_failed'):
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
elif downloadstatus == 'not_found':
log.info('%s not found in downloaders', item['name'])
rel.status_id = ignored_status.get('id')
db.commit()
except:
log.error('Failed checking for release in downloader: %s', traceback.format_exc())
# Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd
if scan_required:
fireEvent('renamer.scan')
self.checking_snatched = False
return True

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

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

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

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

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

@ -1,6 +1,6 @@
from couchpotato import get_session
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.request import jsonified, getParam
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
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):
@ -54,6 +83,11 @@ class Searcher(Plugin):
Movie.status.has(identifier = 'active')
).all()
self.in_progress = {
'total': len(movies),
'to_go': len(movies),
}
for movie in movies:
movie_dict = movie.to_dict({
'profile': {'types': {'quality': {}}},
@ -65,15 +99,17 @@ class Searcher(Plugin):
try:
self.single(movie_dict)
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)
except:
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
if self.shuttingDown():
break
#db.close()
self.in_progress = False
def single(self, movie):
@ -192,7 +228,6 @@ class Searcher(Plugin):
fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True)
#db.close()
return ret
def download(self, data, movie, manual = False):
@ -243,7 +278,6 @@ class Searcher(Plugin):
except Exception, e:
log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc()))
#db.close()
return True
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)
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']))
return False
@ -354,9 +388,11 @@ class Searcher(Plugin):
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 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
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
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):
return True
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:
# Prerelease 1 week before theaters
if dates.get('theater') - 604800 < now:

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

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

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

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

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

@ -8,9 +8,9 @@ config = [{
'groups': [
{
'tab': 'renamer',
'subtab': 'trailer',
'name': 'trailer',
'label': 'Download trailer after rename',
'label': 'Download trailer',
'description': 'after rename',
'options': [
{
'name': 'enabled',
@ -24,12 +24,6 @@ config = [{
'type': 'dropdown',
'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 {
background: #5c697b;
padding: 18px 0;
font-size: 23px;
padding: 10px 0;
font-size: 18px;
position: fixed;
top: 0;
margin: 0;
width: 100%;
min-width: 960px;
left: 0;
z-index: 2;
box-shadow: 0 0 50px rgba(0,0,0,0.55);
@ -36,7 +37,7 @@
display: inline-block;
}
.page.wizard .tabs li a {
padding: 20px 30px;
padding: 20px 10px;
}
.page.wizard .tab_wrapper .pointer {
@ -45,7 +46,7 @@
border-top: 10px solid #5c697b;
display: block;
position: absolute;
top: 60px;
top: 44px;
}
.page.wizard .tab_content {
@ -58,11 +59,25 @@
.page.wizard .wgroup_finish {
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 {
padding: 20px;
font-size: 25px;
margin: 10px 30px;
margin: 10px 30px 80px;
display: block;
text-align: center;
}
.page.wizard .tab_nzb_providers {
margin: 20px 0 0 0;
}

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

@ -37,21 +37,37 @@ Page.Wizard = new Class({
},
'downloaders': {
'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': {
'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': {
'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': {
'title': 'Finish Up',
'description': 'Are you done? Did you fill in everything as much as possible? Yes, ok gogogo!',
'title': 'Finishing Up',
'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(
new Element('a.button.green', {
'styles': {
'margin-top': 20
},
'text': 'I\'m ready to start the awesomeness, wow this button is big and green!',
'events': {
'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){
var self = this;
@ -110,7 +126,8 @@ Page.Wizard = new Class({
var form = self.el.getElement('.uniForm');
var tabs = self.el.getElement('.tabs');
self.groups.each(function(group){
self.groups.each(function(group, nr){
if(self.headers[group]){
group_container = new Element('.wgroup_'+group, {
'styles': {
@ -120,6 +137,14 @@ Page.Wizard = new Class({
'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(
new Element('h1', {
'text': self.headers[group].title
@ -127,15 +152,40 @@ Page.Wizard = new Class({
self.headers[group].description ? new Element('span.description', {
'html': self.headers[group].description
}) : null,
self.headers[group].content ? self.headers[group].content : null
content ? (typeOf(content) == 'function' ? content() : content) : null
).inject(form);
}
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){
tab_navigation.inject(tabs); // Tab navigation
self.el.getElement('.tab_'+group).inject(group_container); // Tab content
if(self.headers[group]){
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
if(tab_navigation.getElement && self.headers[group]){
var a = tab_navigation.getElement('a');
a.set('text', (self.headers[group].label || group).capitalize());
var url_split = a.get('href').split('wizard')[1].split('/');
@ -163,6 +213,8 @@ Page.Wizard = new Class({
// Hide retention
self.el.getElement('.tab_searcher').hide();
self.el.getElement('.t_searcher').hide();
self.el.getElement('.t_nzb_providers').hide();
self.el.getElement('.t_torrent_providers').hide();
// Add pointer
new Element('.tab_wrapper').wraps(tabs).adopt(

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

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

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

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

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

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

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

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

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

@ -8,8 +8,9 @@ config = [{
'groups': [
{
'tab': 'searcher',
'subtab': 'providers',
'subtab': 'nzb_providers',
'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>',
'wizard': True,
'options': [

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

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

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

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

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

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

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

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

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

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

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

@ -5,32 +5,34 @@ def start():
config = [{
'name': 'passthepopcorn',
'groups': [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'PassThePopcorn',
'description': 'See <a href="http://passthepopcorn.me">PassThePopcorn.me</a>',
'options': [
{
'name': 'enabled',
'type': 'enabler',
'default': False
},
{
'name': 'domain',
'advanced': True,
'label': 'Proxy server',
'description': 'Domain for requests (HTTPS only!), keep empty to use default (tls.passthepopcorn.me).',
},
{
'name': 'username',
'default': '',
},
{
'name': 'password',
'default': '',
'type': 'password',
}
],
}]
'groups': [
{
'tab': 'searcher',
'subtab': 'torrent_providers',
'name': 'PassThePopcorn',
'description': 'See <a href="http://passthepopcorn.me">PassThePopcorn.me</a>',
'options': [
{
'name': 'enabled',
'type': 'enabler',
'default': False
},
{
'name': 'domain',
'advanced': True,
'label': 'Proxy server',
'description': 'Domain for requests (HTTPS only!), keep empty to use default (tls.passthepopcorn.me).',
},
{
'name': 'username',
'default': '',
},
{
'name': 'password',
'default': '',
'type': 'password',
}
],
}
]
}]

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

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

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

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

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

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

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

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

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

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

2
couchpotato/core/settings/__init__.py

@ -204,7 +204,6 @@ class Settings(object):
except:
pass
#db.close()
return prop
def setProperty(self, identifier, value = ''):
@ -221,4 +220,3 @@ class Settings(object):
p.value = toUnicode(value)
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))
warnings.showwarning = customwarn
# Check if database exists
db = Env.get('db_path')
db_exists = os.path.isfile(db_path)
# Load configs & plugins
loader = Env.get('loader')
loader.preload(root = base_path)
loader.run()
# Load migrations
initialize = True
db = Env.get('db_path')
if os.path.isfile(db_path):
initialize = False
if db_exists:
from migrate.versioning.api import version_control, db_version, version, upgrade
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
setup()
if initialize:
# Fill database with needed stuff
if not db_exists:
fireEvent('app.initialize', in_order = True)
# 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);
else
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'
})
)
);
}
});

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

@ -211,7 +211,7 @@ Page.Settings = new Class({
if(self.tabs[tab_name] && 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(
new Element('a', {
'href': App.createUrl(self.name+'/'+tab_name),
@ -244,7 +244,7 @@ Page.Settings = new Class({
if(!parent_tab.subtabs_el)
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(
new Element('a', {
'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 || '')
}).adopt(
new Element('h2', {
'text': (group.label || group.name).capitalize()
'text': group.label || (group.name).capitalize()
}).adopt(
new Element('span.hint', {
'html': group.description || ''
@ -635,7 +635,7 @@ Option.Directory = new Class({
),
self.dir_list = new Element('ul', {
'events': {
'click:relay(li)': function(e, el){
'click:relay(li:not(.empty))': function(e, el){
(e).preventDefault();
self.selectDirectory(el.get('data-value'))
},
@ -701,12 +701,24 @@ Option.Directory = new Class({
fillBrowser: function(json){
var self = this;
var v = self.input.get('text');
self.data = json;
var v = self.getValue();
var previous_dir = self.getParentDir();
if(v == '')
self.input.set('text', json.home);
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('html', '&laquo; '+self.getCurrentDirname(previous_dir))
self.back_button.set('html', '&laquo; '+prev_dirname)
self.back_button.show()
}
else {
@ -719,23 +731,24 @@ Option.Directory = new Class({
else
self.cached[v] = json;
setTimeout(function(){
self.dir_list.empty();
self.dir_list.empty();
if(json.dirs.length > 0)
json.dirs.each(function(dir){
if(dir.indexOf(v) != -1){
new Element('li', {
'data-value': dir,
'text': self.getCurrentDirname(dir)
}).inject(self.dir_list)
}
new Element('li', {
'data-value': dir,
'text': self.getCurrentDirname(dir)
}).inject(self.dir_list)
});
}, 50);
else
new Element('li.empty', {
'text': 'Selected folder is empty'
}).inject(self.dir_list)
},
getDirs: function(){
var self = this;
var c = self.input.get('text');
var c = self.getValue();
if(self.cached[c] && self.use_cache){
self.fillBrowser()
@ -754,7 +767,10 @@ Option.Directory = new Class({
getParentDir: function(dir){
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 dirs = v.split(sep);
if(dirs.pop() == '')

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

@ -10,16 +10,73 @@ Page.Wanted = new Class({
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
self.wanted = new MovieList({
'identifier': 'wanted',
'status': 'active',
'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);
// 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', {
'duration': 300,
'onComplete': function(){
movie.destroy();
self.movie.destroy()
}
});
movie.tween('height', 0);

12
couchpotato/static/style/main.css

@ -376,22 +376,12 @@ body > .spinner, .mask{
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 {
display: none;
background: #282d34;
border: 1px solid #1f242b;
position: absolute;
margin: 30px 0 0 0;
margin: 28px 0 0 0;
box-shadow: 0 20px 20px -10px rgba(0,0,0,0.4);
border-radius:3px;
z-index: 3;

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

@ -236,6 +236,19 @@
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 {
clear: both;
padding: 4% 4% 2%;
@ -319,12 +332,11 @@
.page .tag_input > ul {
list-style: none;
padding: 3px 0;
border-radius: 3px;
cursor: text;
width: 30%;
margin: 0 !important;
height: 27px;
min-height: 27px;
line-height: 0;
display: inline-block;
}
@ -339,7 +351,7 @@
min-width: 2px;
font-size: 12px;
padding: 0;
margin: 0 !important;
margin: 4px 0 0 !important;
border-width: 0;
background: 0;
line-height: 20px;
@ -400,7 +412,7 @@
margin: -9px 0 0 -16px;
border-radius: 30px 30px 0 0;
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,
#5b9bd1 0%,
#5b9bd1 100%

Loading…
Cancel
Save