This commit is contained in:
Ruud Burger
2011-08-17 14:00:56 +02:00
33 changed files with 708 additions and 365 deletions
+2 -1
View File
@@ -48,6 +48,7 @@ def cmd_couchpotato(base_path, args):
Env.get('settings').setFile(os.path.join(options.data_dir, 'settings.conf'))
Env.set('app_dir', base_path)
Env.set('data_dir', options.data_dir)
Env.set('log_path', os.path.join(log_dir, 'CouchPotato.log'))
Env.set('db_path', 'sqlite:///' + os.path.join(options.data_dir, 'couchpotato.db'))
Env.set('cache_dir', os.path.join(options.data_dir, 'cache'))
Env.set('cache', FileSystemCache(os.path.join(Env.get('cache_dir'), 'python')))
@@ -73,7 +74,7 @@ def cmd_couchpotato(base_path, args):
logger.addHandler(hdlr)
# To file
hdlr2 = handlers.RotatingFileHandler(os.path.join(log_dir, 'CouchPotato.log'), 'a', 5000000, 4)
hdlr2 = handlers.RotatingFileHandler(Env.get('log_path'), 'a', 500000, 10)
hdlr2.setFormatter(formatter)
logger.addHandler(hdlr2)
@@ -13,7 +13,7 @@ class Blackhole(Downloader):
type = ['nzb', 'torrent']
def download(self, data = {}):
def download(self, data = {}, movie = {}):
if self.isDisabled() or not self.isCorrectType(data.get('type')):
return
@@ -23,7 +23,8 @@ class Blackhole(Downloader):
if not directory or not os.path.isdir(directory):
log.error('No directory set for blackhole %s download.' % data.get('type'))
else:
fullPath = os.path.join(directory, toSafeString(data.get('name')) + '.' + data.get('type'))
cp_tag = '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else ''
fullPath = os.path.join(directory, '%s%s.%s' % (toSafeString(data.get('name')), cp_tag , data.get('type')))
try:
if not os.path.isfile(fullPath):
@@ -26,6 +26,11 @@ config = [{
'label': 'Api Key',
'description': 'Used for all calls to Sabnzbd.',
},
{
'name': 'category',
'label': 'Category',
'description': 'The category CP places the nzb in. Like <strong>movies</strong> or <strong>couchpotato</strong>',
},
{
'advanced': True,
'name': 'pp_directory',
+8 -5
View File
@@ -14,7 +14,7 @@ class Sabnzbd(Downloader):
type = ['nzb']
def download(self, data = {}):
def download(self, data = {}, movie = {}):
if self.isDisabled() or not self.isCorrectType(data.get('type')):
return
@@ -34,11 +34,14 @@ class Sabnzbd(Downloader):
else:
pp = False
cp_tag = '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else ''
params = {
'apikey': self.conf('api_key'),
'cat': self.conf('category'),
'mode': 'addurl',
'name': data.get('url')
'name': data.get('url'),
'nzbname': '%s%s' % (data.get('name'), cp_tag),
}
# sabNzbd complains about "invalid archive file" for newzbin urls
@@ -53,9 +56,9 @@ class Sabnzbd(Downloader):
log.info("URL: " + url)
try:
r = urllib2.urlopen(url, timeout = 30)
except:
log.error("Unable to connect to SAB.")
r = urllib2.urlopen(url)
except Exception, e:
log.error("Unable to connect to SAB: %s" % e)
return False
result = r.read().strip()
+1 -1
View File
@@ -44,7 +44,7 @@ def fireEvent(name, *args, **kwargs):
if single and not merge:
results = None
if result[0][0] == True and result[0][1]:
if result[0][0] is True and result[0][1] is not None:
results = result[0][1]
elif result[0][1]:
errorHandler(result[0][1])
+8 -6
View File
@@ -13,9 +13,9 @@ class CoreNotifier(Plugin):
messages = []
def __init__(self):
addEvent('notify', self.notify)
addEvent('notify.core_notifier', self.notify)
addEvent('core_notifier.frontend', self.frontend)
addEvent('notify.core', self.frontend)
addApiView('core_notifier.listener', self.listener)
@@ -29,7 +29,6 @@ class CoreNotifier(Plugin):
})
def frontend(self, type = 'notification', data = {}):
self.messages.append({
'time': time.time(),
'type': type,
@@ -38,12 +37,15 @@ class CoreNotifier(Plugin):
def listener(self):
messages = []
for message in self.messages:
print message['time'], (time.time() - 5)
#delete message older then 15s
if message['time'] < (time.time() - 15):
del message
if message['time'] > (time.time() - 15):
messages.append(message)
self.messages = []
return jsonified({
'success': True,
'result': self.messages,
'result': messages,
})
@@ -7,7 +7,7 @@ var NotificationBase = new Class({
var self = this;
self.setOptions(options);
//App.addEvent('load', self.request.bind(self));
App.addEvent('load', self.request.bind(self));
self.addEvent('notification', self.notify.bind(self))
@@ -33,7 +33,7 @@ var NotificationBase = new Class({
var self = this;
Array.each(json.result, function(result){
self.fireEvent(result.type, result.data)
App.fireEvent(result.type, result.data)
})
}
+5 -4
View File
@@ -96,9 +96,10 @@ class LibraryPlugin(Plugin):
library.files.append(file)
db.commit()
except:
pass
#log.debug('Failed to attach to library: %s' % traceback.format_exc())
log.debug('Failed to attach to library: %s' % traceback.format_exc())
fireEvent('library.update.after')
library_dict = library.to_dict({'titles': {}, 'files':{}})
return library.to_dict({'titles': {}, 'files':{}})
fireEvent('notify.core', type = 'library.update', data = library_dict)
return library_dict
+6
View File
@@ -0,0 +1,6 @@
from .main import Logging
def start():
return Logging()
config = []
+30
View File
@@ -0,0 +1,30 @@
from couchpotato.api import addApiView
from couchpotato.core.helpers.request import jsonified, getParam
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
class Logging(Plugin):
def __init__(self):
addApiView('logging.get', self.get)
def get(self):
nr = int(getParam('nr', 0))
path = '%s%s' % (Env.get('log_path'), '.%s' % nr if nr > 0 else '')
# Reverse
f = open(path, 'r')
lines = []
for line in f.readlines():
lines.insert(0, line)
log = ''
for line in lines:
log += line
return jsonified({
'success': True,
'log': log,
})
+3 -3
View File
@@ -38,7 +38,7 @@ class MoviePlugin(Plugin):
movies = []
for movie in results:
temp = movie.to_dict(deep = {
'releases': {'status': {}, 'quality': {}, 'files':{}},
'releases': {'status': {}, 'quality': {}, 'files':{}, 'info': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
})
@@ -68,7 +68,7 @@ class MoviePlugin(Plugin):
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
fireEventAsync('searcher.single', movie.to_dict(deep = {
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'releases': {'status': {}, 'quality': {}, 'files': {}, 'info': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
}))
@@ -119,7 +119,7 @@ class MoviePlugin(Plugin):
db.commit()
movie_dict = m.to_dict(deep = {
'releases': {'status': {}, 'quality': {}},
'releases': {'status': {}, 'quality': {}, 'files': {}, 'info': {}},
'library': {'titles': {}}
})
+37 -23
View File
@@ -3,10 +3,11 @@ var MovieList = new Class({
Implements: [Options],
options: {
navigation: true
navigation: false
},
movies: [],
letters: {},
initialize: function(options){
var self = this;
@@ -19,6 +20,8 @@ var MovieList = new Class({
create: function(){
var self = this;
self.el.empty();
// Create the alphabet nav
if(self.options.navigation)
self.createNavigation();
@@ -29,14 +32,19 @@ var MovieList = new Class({
}, info);
$(m).inject(self.el);
m.fireEvent('injected');
if(self.options.navigation){
var first_char = m.getTitle().substr(0, 1);
self.activateLetter(first_char);
}
});
self.el.addEvents({
'mouseenter:relay(.movie)': function(e, el){
el.addClass('hover')
el.addClass('hover');
},
'mouseleave:relay(.movie)': function(e, el){
el.removeClass('hover')
el.removeClass('hover');
}
});
},
@@ -44,7 +52,6 @@ var MovieList = new Class({
createNavigation: function(){
var self = this;
var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var selected = 'Z';
self.navigation = new Element('div.alph_nav').adopt(
self.alpha = new Element('ul.inlay'),
@@ -54,32 +61,39 @@ var MovieList = new Class({
new Element('li.thumbnails'),
new Element('li.text')
)
).inject(this.el, 'top')
).inject(this.el, 'top');
chars.split('').each(function(c){
new Element('li', {
self.letters[c] = new Element('li', {
'text': c,
'class': c == selected ? 'selected' : ''
}).inject(self.alpha)
})
'class': 'letter_'+c
}).inject(self.alpha);
});
},
activateLetter: function(letter){
this.letters[letter].addClass('active');
},
getMovies: function(status, onComplete){
var self = this
update: function(){
var self = this;
if(self.movies.length == 0)
Api.request('movie.list', {
'data': {
'status': self.options.status
},
'onComplete': function(json){
self.store(json.movies);
self.create();
}
})
else
self.list()
self.getMovies();
},
getMovies: function(){
var self = this;
Api.request('movie.list', {
'data': {
'status': self.options.status
},
'onComplete': function(json){
self.store(json.movies);
self.create();
}
});
},
store: function(movies){
@@ -159,7 +159,11 @@
text-align: center;
cursor: pointer;
margin: 0 -1px 0 0;
color: #666;
}
.movies .alph_nav li.active {
color: #fff;
}
.movies .alph_nav li:hover, .movies .alph_nav li.onlay {
font-weight: bold;
+46 -3
View File
@@ -127,7 +127,8 @@ var MovieAction = new Class({
self.movie = movie;
self.create();
self.el.addClass(self.class_name)
if(self.el)
self.el.addClass(self.class_name)
},
create: function(){},
@@ -141,7 +142,7 @@ var MovieAction = new Class({
},
toElement: function(){
return this.el
return this.el || null
}
});
@@ -173,4 +174,46 @@ var IMDBAction = new Class({
window.open('http://www.imdb.com/title/'+self.id+'/');
}
})
});
var ReleaseAction = new Class({
Extends: MovieAction,
id: null,
create: function(){
var self = this;
self.id = self.movie.get('identifier');
self.el = new Element('a.releases', {
'title': 'Show the releases that are available for ' + self.movie.getTitle(),
'events': {
'click': self.show.bind(self)
}
});
},
show: function(e){
var self = this;
(e).stop();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
$(self.movie.thumbnail).clone(),
self.release_container = new Element('div.releases')
).inject(self.movie, 'top');
Array.each(self.movie.data.releases, function(release){
p(release);
new Element('div', {
'text': release.title
}).inject(self.release_container)
});
}
self.movie.slide('in');
},
});
+1 -1
View File
@@ -13,7 +13,7 @@ log = CPLog(__name__)
class QualityPlugin(Plugin):
qualities = [
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['1080p', 'bd25'], 'allow': [], 'ext':[], 'tags': ['x264', 'h264', 'blu ray']},
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['x264', 'h264', 'bluray']},
{'identifier': '1080p', 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
{'identifier': '720p', 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
{'identifier': 'brrip', 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
@@ -0,0 +1,6 @@
from .main import Release
def start():
return Release()
config = []
+75
View File
@@ -0,0 +1,75 @@
from couchpotato import get_session
from couchpotato.core.event import fireEvent, addEvent
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import File, Release, Movie
from sqlalchemy.sql.expression import and_, or_
log = CPLog(__name__)
class Release(Plugin):
def __init__(self):
addEvent('release.add', self.add)
def add(self, group):
db = get_session()
identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data'].get('audio', 'unknown'), group['meta_data']['quality']['identifier'])
# Add movie
done_status = fireEvent('status.get', 'done', single = True)
movie = db.query(Movie).filter_by(library_id = group['library'].get('id')).first()
if not movie:
movie = Movie(
library_id = group['library'].get('id'),
profile_id = 0,
status_id = done_status.get('id')
)
db.add(movie)
db.commit()
# Add release
snatched_status = fireEvent('status.get', 'snatched', single = True)
release = db.query(Release).filter(
or_(
Release.identifier == identifier,
and_(Release.identifier.startswith(group['library']['identifier'], Release.status_id == snatched_status.get('id')))
)
).first()
if not release:
release = Release(
identifier = identifier,
movie = movie,
quality_id = group['meta_data']['quality'].get('id'),
status_id = done_status.get('id')
)
db.add(release)
db.commit()
# Add each file type
for type in group['files']:
for file in group['files'][type]:
added_file = self.saveFile(file, type = type, include_media_info = type is 'movie')
try:
added_file = db.query(File).filter_by(id = added_file.get('id')).one()
release.files.append(added_file)
db.commit()
except Exception, e:
log.debug('Failed to attach "%s" to release: %s' % (file, e))
db.remove()
def saveFile(self, file, type = 'unknown', include_media_info = False):
properties = {}
# Get media info for files
if include_media_info:
properties = {}
# Check database and update/insert if necessary
return fireEvent('file.add', path = file, part = self.getPartNumber(file), type = self.file_types[type], properties = properties, single = True)
+31 -20
View File
@@ -18,13 +18,14 @@ class Renamer(Plugin):
def __init__(self):
addEvent('renamer.scan', self.scan)
#addEvent('app.load', self.scan)
addEvent('app.load', self.scan)
#fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every'))
fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every'))
def scan(self):
groups = fireEvent('scanner.scan', folder = self.conf('from'), single = True)
if groups is None: return
destination = self.conf('to')
folder_name = self.conf('folder_name')
@@ -151,42 +152,52 @@ class Renamer(Plugin):
# Before renaming, remove the lower quality files
db = get_session()
library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
done_status = fireEvent('status.get', 'done', single = True)
for movie in library.movies:
for release in movie.releases:
if release.quality.order < group['meta_data']['quality']['order']:
log.info('Removing older release for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
elif release.quality.order is group['meta_data']['quality']['order']:
log.info('Same quality release already exists for %s, with quality %s. Assuming repack.' % (movie.library.titles[0].title, release.quality.label))
else:
log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
elif release.status_id is done_status.get('id'):
if release.quality.order is group['meta_data']['quality']['order']:
log.info('Same quality release already exists for %s, with quality %s. Assuming repack.' % (movie.library.titles[0].title, release.quality.label))
else:
log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
# Add _EXISTS_ to the parent dir
if group['dirname']:
for rename_me in rename_files: # Don't rename anything in this group
rename_files[rename_me] = None
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_EXISTS_%s' % group['dirname'])
else: # Add it to filename
for rename_me in rename_files:
filename = os.path.basename(rename_me)
rename_files[rename_me] = rename_me.replace(filename, '_EXISTS_%s' % filename)
# Add _EXISTS_ to the parent dir
if group['dirname']:
for rename_me in rename_files: # Don't rename anything in this group
rename_files[rename_me] = None
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_EXISTS_%s' % group['dirname'])
else: # Add it to filename
for rename_me in rename_files:
filename = os.path.basename(rename_me)
rename_files[rename_me] = rename_me.replace(filename, '_EXISTS_%s' % filename)
break
break
for file in release.files:
log.info('Removing "%s"' % file.path)
# Rename
for rename_me in rename_files:
if rename_files[rename_me]:
log.info('Renaming "%s" to "%s"' % (rename_me, rename_files[rename_me]))
for src in rename_files:
if rename_files[src]:
path = os.path.dirname(rename_files[rename_me])
dst = rename_files[src]
log.info('Renaming "%s" to "%s"' % (src, dst))
path = os.path.dirname(dst)
try:
if not os.path.isdir(path): os.makedirs(path)
except:
log.error('Failed creating dir %s: %s' % (path, traceback.format_exc()))
continue
try:
shutil.move(src, dst)
except:
log.error('Failed moving the file "%s" : %s' % (os.path.basename(src), traceback.format_exc()))
#print rename_me, rename_files[rename_me]
# Search for trailers
+6 -61
View File
@@ -7,7 +7,7 @@ from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import File, Release, Movie
from couchpotato.environment import Env
from flask.helpers import json
from themoviedb.tmdb import opensubtitleHashFile
from sqlalchemy.sql.expression import and_, or_
import os
import re
import subprocess
@@ -70,6 +70,7 @@ class Scanner(Plugin):
def __init__(self):
#addEvent('app.load', self.scanLibrary)
addEvent('scanner.create_file_identifier', self.createStringIdentifier)
addEvent('scanner.scan', self.scan)
@@ -95,7 +96,7 @@ class Scanner(Plugin):
#library = db.query(Library).filter_by(id = library.get('id')).one()
# Add release
self.addRelease(group)
fireEvent('release.add', group = group)
# Add identifier for library update
update_after.append(group['library'].get('identifier'))
@@ -133,7 +134,7 @@ class Scanner(Plugin):
is_dvd_file = self.isDVDFile(file_path)
if os.path.getsize(file_path) > self.minimal_filesize['media'] or is_dvd_file: # Minimal 300MB files or is DVD file
identifier = self.createFileIdentifier(file_path, folder, exclude_filename = is_dvd_file)
identifier = self.createStringIdentifier(file_path, folder, exclude_filename = is_dvd_file)
if not movie_files.get(identifier):
movie_files[identifier] = {
@@ -221,51 +222,6 @@ class Scanner(Plugin):
return movie_files
def addRelease(self, group):
db = get_session()
identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data'].get('audio', 'unknown'), group['meta_data']['quality']['identifier'])
# Add movie
done_status = fireEvent('status.get', 'done', single = True)
movie = db.query(Movie).filter_by(library_id = group['library'].get('id')).first()
if not movie:
movie = Movie(
library_id = group['library'].get('id'),
profile_id = 0,
status_id = done_status.get('id')
)
db.add(movie)
db.commit()
# Add release
release = db.query(Release).filter_by(identifier = identifier).first()
if not release:
release = Release(
identifier = identifier,
movie = movie,
quality_id = group['meta_data']['quality'].get('id'),
status_id = done_status.get('id')
)
db.add(release)
db.commit()
# Add each file type
for type in group['files']:
for file in group['files'][type]:
added_file = self.saveFile(file, type = type, include_media_info = type is 'movie')
try:
added_file = db.query(File).filter_by(id = added_file.get('id')).one()
release.files.append(added_file)
db.commit()
except Exception, e:
log.debug('Failed to attach "%s" to release: %s' % (file, e))
db.remove()
def getMetaData(self, group):
data = {}
@@ -374,17 +330,6 @@ class Scanner(Plugin):
log.error('No imdb_id found for %s.' % group['identifiers'])
return {}
def saveFile(self, file, type = 'unknown', include_media_info = False):
properties = {}
# Get media info for files
if include_media_info:
properties = {}
# Check database and update/insert if necessary
return fireEvent('file.add', path = file, part = self.getPartNumber(file), type = self.file_types[type], properties = properties, single = True)
def getCPImdb(self, string):
try:
@@ -501,9 +446,9 @@ class Scanner(Plugin):
return False
def getGroupFiles(self, identifier, folder, file_pile):
return set(filter(lambda s:identifier in self.createFileIdentifier(s, folder), file_pile))
return set(filter(lambda s:identifier in self.createStringIdentifier(s, folder), file_pile))
def createFileIdentifier(self, file_path, folder, exclude_filename = False):
def createStringIdentifier(self, file_path, folder = '', exclude_filename = False):
identifier = file_path.replace(folder, '') # root folder
identifier = os.path.splitext(identifier)[0] # ext
+9 -11
View File
@@ -1,14 +1,16 @@
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.encoding import simplifyString
from couchpotato.environment import Env
import re
name_scores = [
'proper:2', 'repack:2',
'proper:5', 'repack:5',
'unrated:1',
'x264:1',
'x264:1', 'h264:1',
'DTS:4', 'AC3:2',
'720p:10', '1080p:10', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1',
'metis:1', 'diamond:1', 'wiki:1', 'CBGB:1',
'720p:10', '1080p:10', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1', 'bd50:1', 'bd25:1',
'imbt:1', 'cocain:1', 'vomit:1', 'fico:1', 'arrow:1', 'pukka:1', 'prism:1', 'devise:1', 'esir:1',
'metis:1', 'diamond:1', 'wiki:1', 'cbgb:1', 'crossbow:1', 'sinners:1', 'amiable:1', 'refined:1', 'twizted:1', 'felony:1', 'hubris:1', 'machd:1',
'german:-10', 'french:-10', 'spanish:-10', 'swesub:-20', 'danish:-10'
]
@@ -40,12 +42,8 @@ def nameScore(name, year):
def nameRatioScore(nzb_name, movie_name):
nzb_words = re.split('\W+', simplifyString(nzb_name))
nzb_words = re.split('\W+', fireEvent('scanner.create_file_identifier', nzb_name, single = True))
movie_words = re.split('\W+', simplifyString(movie_name))
# Replace .,-_ with space
left_over = len(nzb_words) - len(movie_words)
if 2 <= left_over <= 6:
return 4
else:
return 0
left_over = set(nzb_words) - set(movie_words)
return 10 - len(left_over)
+58 -17
View File
@@ -1,9 +1,10 @@
from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import simplifyString
from couchpotato.core.helpers.variable import md5
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Movie
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
from couchpotato.environment import Env
import re
@@ -19,7 +20,7 @@ class Searcher(Plugin):
# Schedule cronjob
fireEvent('schedule.cron', 'searcher.all', self.all, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
#addEvent('app.load', self.all)
addEvent('app.load', self.all)
def all(self):
@@ -29,48 +30,88 @@ class Searcher(Plugin):
Movie.status.has(identifier = 'active')
).all()
snatched_status = fireEvent('status.get', 'snatched', single = True)
for movie in movies:
success = self.single(movie.to_dict(deep = {
self.single(movie.to_dict(deep = {
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
}))
# Mark as snatched on success
if success:
movie.status_id = snatched_status.get('id')
db.commit()
def single(self, movie):
downloaded_status = fireEvent('status.get', 'downloaded', single = True)
available_status = fireEvent('status.get', 'available', single = True)
snatched_status = fireEvent('status.get', 'snatched', single = True)
successful = False
for type in movie['profile']['types']:
print type
has_better_quality = False
has_better_quality = 0
default_title = movie['library']['titles'][0]['title']
# See if beter quality is available
for release in movie['releases']:
if release['quality']['order'] <= type['quality']['order']:
has_better_quality = True
if release['quality']['order'] <= type['quality']['order'] and release['status_id'] is not available_status.get('id'):
has_better_quality += 1
# Don't search for quality lower then already available.
if not has_better_quality:
if has_better_quality is 0:
log.info('Search for %s in %s' % (movie['library']['titles'][0]['title'], type['quality']['label']))
log.info('Search for %s in %s' % (default_title, type['quality']['label']))
results = fireEvent('provider.yarr.search', movie, type['quality'], merge = True)
sorted_results = sorted(results, key = lambda k: k['score'], reverse = True)
# Add them to this movie releases list
for nzb in sorted_results:
successful = fireEvent('download', data = nzb, single = True)
db = get_session()
rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first()
if not rls:
rls = Release(
identifier = md5(nzb['url']),
movie_id = movie.get('id'),
quality_id = type.get('quality_id'),
status_id = available_status.get('id')
)
db.add(rls)
db.commit()
for info in nzb:
rls_info = ReleaseInfo(
identifier = info,
value = nzb[info]
)
rls.info.append(rls_info)
db.commit()
for nzb in sorted_results:
successful = fireEvent('download', data = nzb, movie = movie, single = True)
if successful:
log.info('Downloading of %s successful.' % nzb.get('name'))
# Mark release as snatched
db = get_session()
rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first()
rls.status_id = snatched_status.get('id')
db.commit()
# Mark movie snatched if quality is finish-checked
if type['finish']:
mvie = db.query(Movie).filter_by(id = movie['id']).first()
mvie.status_id = snatched_status.get('id')
db.commit()
return True
return False
else:
log.info('Better quality (%s) already available or snatched for %s' % (type['quality']['label'], default_title))
break
return False
+1 -1
View File
@@ -118,7 +118,7 @@ class YarrProvider(Provider):
if identifier in qualities:
return ids
return False
return [self.cat_backup_id]
def found(self, new):
log.info('Found: score(%(score)s): %(name)s' % new)
@@ -54,7 +54,7 @@ class Newznab(NZBProvider, RSS):
})
url = "%s&%s" % (self.getUrl(self.urls['search']), arguments)
cache_key = '%s-%s' % (movie['library']['identifier'], cat_id[0])
cache_key = 'newznab.%s.%s' % (movie['library']['identifier'], cat_id[0])
single_cat = (len(cat_id) == 1 and cat_id[0] != self.cat_backup_id)
try:
@@ -0,0 +1,21 @@
from .main import NzbIndex
def start():
return NzbIndex()
config = [{
'name': 'nzbindex',
'groups': [
{
'tab': 'providers',
'name': 'nzbindex',
'description': 'Free provider, but less accurate.',
'options': [
{
'name': 'enabled',
'type': 'enabler',
},
],
},
],
}]
@@ -0,0 +1,97 @@
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import simplifyString
from couchpotato.core.helpers.rss import RSS
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.base import NZBProvider
from dateutil.parser import parse
from urllib import urlencode
from urllib2 import URLError
import time
import xml.etree.ElementTree as XMLTree
log = CPLog(__name__)
class NzbIndex(NZBProvider, RSS):
urls = {
'download': 'http://www.nzbindex.nl/download/%s/%s',
'api': 'http://www.nzbindex.nl/rss/', #http://www.nzbindex.nl/rss/?q=due+date+720p&age=1000&sort=agedesc&minsize=3500&maxsize=10000
}
time_between_searches = 1 # Seconds
def __init__(self):
addEvent('provider.nzb.search', self.search)
addEvent('provider.yarr.search', self.search)
def search(self, movie, quality):
results = []
if self.isDisabled() or not self.isAvailable(self.urls['api']):
return results
arguments = urlencode({
'q': '%s %s' % (simplifyString(movie['library']['titles'][0]['title']), quality.get('identifier')),
'sort': 'agedesc',
'minsize': quality.get('size_min'),
'maxsize': quality.get('size_max'),
'rating': '1',
})
url = "%s?%s" % (self.urls['api'], arguments)
cache_key = 'nzbindex.%s.%s' % (movie['library'].get('identifier'), quality.get('identifier'))
try:
data = self.getCache(cache_key)
if not data:
data = self.urlopen(url)
self.setCache(cache_key, data)
except (IOError, URLError):
log.error('Failed to open %s.' % url)
return results
if data:
try:
try:
data = XMLTree.fromstring(data)
nzbs = self.getElements(data, 'channel/item')
except Exception, e:
log.debug('%s, %s' % (self.getName(), e))
return results
for nzb in nzbs:
enclosure = self.getElements(nzb, 'enclosure')[0].attrib
id = int(self.getTextElement(nzb, "link").split('/')[4])
new = {
'id': id,
'type': 'nzb',
'name': self.getTextElement(nzb, "title"),
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
'size': enclosure['length'],
'url': enclosure['url'],
'detail_url': enclosure['url'].replace('/download/', '/release/'),
'description': self.getTextElement(nzb, "description"),
'check_nzb': True,
}
new['score'] = fireEvent('score.calculate', new, movie, single = True)
is_correct_movie = fireEvent('searcher.correct_movie',
nzb = new, movie = movie, quality = quality,
imdb_results = False, single_category = False, single = True)
if is_correct_movie:
results.append(new)
self.found(new)
return results
except SyntaxError:
log.error('Failed to parse XML response from NZBMatrix.com')
return results
def isEnabled(self):
return NZBProvider.isEnabled(self) and self.conf('enabled')
@@ -53,7 +53,7 @@ class NZBMatrix(NZBProvider, RSS):
url = "%s?%s" % (self.urls['search'], arguments)
log.info('Searching: %s' % url)
cache_key = '%s-%s' % (movie['library'].get('identifier'), cat_ids)
cache_key = 'nzbmatrix.%s.%s' % (movie['library'].get('identifier'), cat_ids)
single_cat = True
try:
+3 -2
View File
@@ -50,7 +50,7 @@ class Nzbs(NZBProvider, RSS):
})
url = "%s?%s" % (self.urls['api'], arguments)
cache_key = '%s-%s' % (movie['library'].get('identifier'), str(cat_id))
cache_key = 'nzbs.%s.%s' % (movie['library'].get('identifier'), str(cat_id))
try:
data = self.getCache(cache_key)
@@ -72,8 +72,9 @@ class Nzbs(NZBProvider, RSS):
for nzb in nzbs:
id = int(self.getTextElement(nzb, "link").partition('nzbid=')[2])
new = {
'id': int(self.getTextElement(nzb, "link").partition('nzbid=')[2]),
'id': id,
'type': 'nzb',
'name': self.getTextElement(nzb, "title"),
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
+1
View File
@@ -19,6 +19,7 @@ class Env:
_data_dir = ""
_cache_dir = ""
_db_path = ""
_log_path = ""
@staticmethod
def doDebug():
+20 -1
View File
@@ -3,6 +3,25 @@ Page.Log = new Class({
Extends: PageBase,
name: 'log',
title: 'Show recent logs.'
title: 'Show recent logs.',
indexAction: function(){
var self = this;
if(self.log) self.log.destroy();
self.log = new Element('div.log', {
'text': 'loading...'
}).inject(self.el)
Api.request('logging.get', {
'data': {
'nr': 0
},
'onComplete': function(json){
self.log.set('html', '<pre>'+json.log+'</pre>')
}
})
}
})
+214 -196
View File
@@ -8,222 +8,240 @@ Page.Wanted = new Class({
indexAction: function(param){
var self = this;
self.list = new MovieList({
'status': 'active',
'actions': Wanted.Action
});
$(self.list).inject(self.el);
if(!self.list){
// Wanted movies
self.wanted = new MovieList({
'status': 'active',
'actions': WantedActions
});
$(self.wanted).inject(self.el);
App.addEvent('library.update', self.wanted.update.bind(self.wanted));
// Snatched movies
self.snatched = new MovieList({
'status': 'snatched',
'actions': SnatchedActions
});
$(self.snatched).inject(self.el);
App.addEvent('library.update', self.snatched.update.bind(self.snatched));
}
}
});
var Wanted = {
'Action': {
'IMBD': IMDBAction
}
}
var WantedActions = {
'IMBD': IMDBAction
//,'releases': ReleaseAction
Wanted.Action.Edit = new Class({
,'Edit': new Class({
Extends: MovieAction,
create: function(){
var self = this;
self.el = new Element('a.edit', {
'title': 'Refresh the movie info and do a forced search',
'events': {
'click': self.editMovie.bind(self)
Extends: MovieAction,
create: function(){
var self = this;
self.el = new Element('a.edit', {
'title': 'Refresh the movie info and do a forced search',
'events': {
'click': self.editMovie.bind(self)
}
});
},
editMovie: function(e){
var self = this;
(e).stop();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
$(self.movie.thumbnail).clone(),
new Element('div.form', {
'styles': {
'line-height': self.movie.getHeight()
}
}).adopt(
self.title_select = new Element('select', {
'name': 'title'
}),
self.profile_select = new Element('select', {
'name': 'profile'
}),
new Element('a.button.edit', {
'text': 'Save',
'events': {
'click': self.save.bind(self)
}
})
)
).inject(self.movie, 'top');
Array.each(self.movie.data.library.titles, function(alt){
new Element('option', {
'text': alt.title
}).inject(self.title_select);
});
Object.each(Quality.profiles, function(profile){
new Element('option', {
'value': profile.id ? profile.id : profile.data.id,
'text': profile.label ? profile.label : profile.data.label
}).inject(self.profile_select);
self.profile_select.set('value', self.movie.profile.get('id'));
});
}
});
self.movie.slide('in');
},
save: function(e){
(e).stop();
var self = this;
Api.request('movie.edit', {
'data': {
'id': self.movie.get('id'),
'default_title': self.title_select.get('value'),
'profile_id': self.profile_select.get('value')
},
'useSpinner': true,
'spinnerTarget': $(self.movie),
'onComplete': function(){
self.movie.quality.set('text', self.profile_select.getSelected()[0].get('text'));
self.movie.title.set('text', self.title_select.getSelected()[0].get('text'));
}
});
self.movie.slide('out');
}
})
},
,'Refresh': new Class({
editMovie: function(e){
var self = this;
(e).stop();
Extends: MovieAction,
create: function(){
var self = this;
self.el = new Element('a.refresh', {
'title': 'Refresh the movie info and do a forced search',
'events': {
'click': self.doSearch.bind(self)
}
});
},
doSearch: function(e){
var self = this;
(e).stop();
Api.request('movie.refresh', {
'data': {
'id': self.movie.get('id')
}
});
}
})
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
$(self.movie.thumbnail).clone(),
new Element('div.form', {
,'Delete': new Class({
Extends: MovieAction,
Implements: [Chain],
create: function(){
var self = this;
self.el = new Element('a.delete', {
'title': 'Remove the movie from your wanted list',
'events': {
'click': self.showConfirm.bind(self)
}
});
},
showConfirm: function(e){
var self = this;
(e).stop();
if(!self.delete_container){
self.delete_container = new Element('div.delete_container', {
'styles': {
'line-height': self.movie.getHeight()
}
}).adopt(
self.title_select = new Element('select', {
'name': 'title'
}),
self.profile_select = new Element('select', {
'name': 'profile'
}),
new Element('a.button.edit', {
'text': 'Save',
new Element('a.cancel', {
'text': 'Cancel',
'events': {
'click': self.save.bind(self)
'click': self.hideConfirm.bind(self)
}
}),
new Element('span.or', {
'text': 'or'
}),
new Element('a.button.delete', {
'text': 'Delete ' + self.movie.title.get('text'),
'events': {
'click': self.del.bind(self)
}
})
)
).inject(self.movie, 'top');
Array.each(self.movie.data.library.titles, function(alt){
new Element('option', {
'text': alt.title
}).inject(self.title_select)
});
Object.each(Quality.profiles, function(profile){
new Element('option', {
'value': profile.id ? profile.id : profile.data.id,
'text': profile.label ? profile.label : profile.data.label
}).inject(self.profile_select);
self.profile_select.set('value', self.movie.profile.get('id'));
});
}
self.movie.slide('in');
},
save: function(e){
(e).stop();
var self = this;
Api.request('movie.edit', {
'data': {
'id': self.movie.get('id'),
'default_title': self.title_select.get('value'),
'profile_id': self.profile_select.get('value')
},
'useSpinner': true,
'spinnerTarget': $(self.movie),
'onComplete': function(){
self.movie.quality.set('text', self.profile_select.getSelected()[0].get('text'))
self.movie.title.set('text', self.title_select.getSelected()[0].get('text'))
).inject(self.movie, 'top');
}
});
self.movie.slide('out');
}
})
Wanted.Action.Refresh = new Class({
Extends: MovieAction,
create: function(){
var self = this;
self.el = new Element('a.refresh', {
'title': 'Refresh the movie info and do a forced search',
'events': {
'click': self.doSearch.bind(self)
}
});
},
doSearch: function(e){
var self = this;
(e).stop();
Api.request('movie.refresh', {
'data': {
'id': self.movie.get('id')
}
})
}
})
Wanted.Action.Delete = new Class({
Extends: MovieAction,
Implements: [Chain],
create: function(){
var self = this;
self.el = new Element('a.delete', {
'title': 'Remove the movie from your wanted list',
'events': {
'click': self.showConfirm.bind(self)
}
});
},
showConfirm: function(e){
var self = this;
(e).stop();
if(!self.delete_container){
self.delete_container = new Element('div.delete_container', {
'styles': {
'line-height': self.movie.getHeight()
self.movie.slide('in');
},
hideConfirm: function(e){
var self = this;
(e).stop();
self.movie.slide('out');
},
del: function(e){
(e).stop();
var self = this;
var movie = $(self.movie);
self.chain(
function(){
$(movie).mask().addClass('loading');
self.callChain();
},
function(){
Api.request('movie.delete', {
'data': {
'id': self.movie.get('id')
},
'onComplete': function(){
movie.set('tween', {
'onComplete': function(){
movie.destroy();
}
});
movie.tween('height', 0);
}
});
}
}).adopt(
new Element('a.cancel', {
'text': 'Cancel',
'events': {
'click': self.hideConfirm.bind(self)
}
}),
new Element('span.or', {
'text': 'or'
}),
new Element('a.button.delete', {
'text': 'Delete ' + self.movie.title.get('text'),
'events': {
'click': self.del.bind(self)
}
})
).inject(self.movie, 'top')
);
self.callChain();
}
self.movie.slide('in');
})
};
},
hideConfirm: function(e){
var self = this;
(e).stop();
self.movie.slide('out');
},
del: function(e){
(e).stop()
var self = this;
var movie = $(self.movie);
self.chain(
function(){
$(movie).mask().addClass('loading')
self.callChain();
},
function(){
Api.request('movie.delete', {
'data': {
'id': self.movie.get('id')
},
'onComplete': function(){
movie.set('tween', {
'onComplete': function(){
movie.destroy();
}
})
movie.tween('height', 0)
}
})
}
);
self.callChain();
}
})
var SnatchedActions = {
'IMBD': IMDBAction
,'Releases': ReleaseAction
,'Delete': WantedActions.Delete
};
+3 -3
View File
@@ -31,10 +31,10 @@
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/wanted.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/settings.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/log.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/soon.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/manage.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/settings.js') }}"></script>
<!--<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/soon.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/manage.js') }}"></script>-->
<link href="{{ url_for('.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" />
<link rel="apple-touch-icon" href="{{ url_for('.static', filename='images/homescreen.png') }}" />