Merge branch 'refs/heads/develop'
This commit is contained in:
@@ -1,10 +1,39 @@
|
||||
from flask.blueprints import Blueprint
|
||||
from flask.helpers import url_for
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import RequestHandler, asynchronous
|
||||
from werkzeug.utils import redirect
|
||||
|
||||
api = Blueprint('api', __name__)
|
||||
api_docs = {}
|
||||
api_docs_missing = []
|
||||
api_nonblock = {}
|
||||
|
||||
|
||||
class NonBlockHandler(RequestHandler):
|
||||
stoppers = []
|
||||
|
||||
@asynchronous
|
||||
def get(self, route):
|
||||
cls = NonBlockHandler
|
||||
start, stop = api_nonblock[route]
|
||||
cls.stoppers.append(stop)
|
||||
|
||||
start(self.onNewMessage, last_id = self.get_argument("last_id", None))
|
||||
|
||||
def onNewMessage(self, response):
|
||||
if self.request.connection.stream.closed():
|
||||
return
|
||||
self.finish(response)
|
||||
|
||||
def on_connection_close(self):
|
||||
cls = NonBlockHandler
|
||||
|
||||
for stop in cls.stoppers:
|
||||
stop(self.onNewMessage)
|
||||
|
||||
cls.stoppers = []
|
||||
|
||||
|
||||
def addApiView(route, func, static = False, docs = None, **kwargs):
|
||||
api.add_url_rule(route + ('' if static else '/'), endpoint = route.replace('.', '::') if route else 'index', view_func = func, **kwargs)
|
||||
@@ -13,6 +42,14 @@ def addApiView(route, func, static = False, docs = None, **kwargs):
|
||||
else:
|
||||
api_docs_missing.append(route)
|
||||
|
||||
def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
|
||||
api_nonblock[route] = func_tuple
|
||||
|
||||
if docs:
|
||||
api_docs[route[4:] if route[0:4] == 'api.' else route] = docs
|
||||
else:
|
||||
api_docs_missing.append(route)
|
||||
|
||||
""" Api view """
|
||||
def index():
|
||||
index_url = url_for('web.index')
|
||||
|
||||
@@ -114,7 +114,6 @@ class Core(Plugin):
|
||||
log.debug('Save to shutdown/restart')
|
||||
|
||||
try:
|
||||
Env.get('httpserver').stop()
|
||||
IOLoop.instance().stop()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
@@ -28,7 +28,7 @@ class Updater(Plugin):
|
||||
else:
|
||||
self.updater = SourceUpdater()
|
||||
|
||||
fireEvent('schedule.interval', 'updater.check', self.check, hours = 6)
|
||||
fireEvent('schedule.interval', 'updater.check', self.autoUpdate, hours = 6)
|
||||
addEvent('app.load', self.check)
|
||||
addEvent('updater.info', self.info)
|
||||
|
||||
@@ -48,17 +48,20 @@ class Updater(Plugin):
|
||||
'return': {'type': 'see updater.info'}
|
||||
})
|
||||
|
||||
def autoUpdate(self):
|
||||
if self.check() and self.conf('automatic') and not self.updater.update_failed:
|
||||
self.updater.doUpdate()
|
||||
|
||||
def check(self):
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
if self.updater.check():
|
||||
if self.conf('automatic') and not self.updater.update_failed:
|
||||
if self.updater.doUpdate():
|
||||
fireEventAsync('app.restart')
|
||||
else:
|
||||
if self.conf('notification'):
|
||||
fireEvent('updater.available', message = 'A new update is available', data = self.updater.info())
|
||||
if self.conf('notification') and not self.conf('automatic'):
|
||||
fireEvent('updater.available', message = 'A new update is available', data = self.updater.info())
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def info(self):
|
||||
return self.updater.info()
|
||||
@@ -67,12 +70,22 @@ class Updater(Plugin):
|
||||
return jsonified(self.updater.info())
|
||||
|
||||
def checkView(self):
|
||||
self.check()
|
||||
return self.updater.getInfo()
|
||||
return jsonified({
|
||||
'update_available': self.check(),
|
||||
'info': self.updater.info()
|
||||
})
|
||||
|
||||
def doUpdateView(self):
|
||||
|
||||
self.check()
|
||||
if not self.update_version:
|
||||
log.error('Trying to update when no update is available.')
|
||||
success = False
|
||||
else:
|
||||
success = self.updater.doUpdate()
|
||||
|
||||
return jsonified({
|
||||
'success': self.updater.doUpdate()
|
||||
'success': success
|
||||
})
|
||||
|
||||
|
||||
@@ -137,6 +150,7 @@ class GitUpdater(BaseUpdater):
|
||||
self.repo = LocalRepository(Env.get('app_dir'), command = git_command)
|
||||
|
||||
def doUpdate(self):
|
||||
|
||||
try:
|
||||
log.debug('Stashing local changes')
|
||||
self.repo.saveStash()
|
||||
@@ -152,6 +166,8 @@ class GitUpdater(BaseUpdater):
|
||||
version_date = datetime.fromtimestamp(info['update_version']['date'])
|
||||
fireEvent('updater.updated', 'Updated to a new version with hash "%s", this version is from %s' % (info['update_version']['hash'], version_date), data = info)
|
||||
|
||||
fireEventAsync('app.restart')
|
||||
|
||||
return True
|
||||
except:
|
||||
log.error('Failed updating via GIT: %s' % traceback.format_exc())
|
||||
@@ -243,6 +259,8 @@ class SourceUpdater(BaseUpdater):
|
||||
# Write update version to file
|
||||
self.createFile(self.version_file, json.dumps(self.update_version))
|
||||
|
||||
fireEventAsync('app.restart')
|
||||
|
||||
return True
|
||||
except:
|
||||
log.error('Failed updating: %s' % traceback.format_exc())
|
||||
|
||||
@@ -16,7 +16,15 @@ var UpdaterBase = new Class({
|
||||
var self = this;
|
||||
|
||||
Api.request('updater.check', {
|
||||
'onComplete': onComplete || Function.from()
|
||||
'onComplete': function(json){
|
||||
if(onComplete)
|
||||
onComplete(json);
|
||||
|
||||
if(json.update_available)
|
||||
self.doUpdate();
|
||||
else
|
||||
App.unBlockPage()
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
@@ -52,7 +60,7 @@ var UpdaterBase = new Class({
|
||||
createMessage: function(data){
|
||||
var self = this;
|
||||
|
||||
var changelog = 'https://github.com/'+data.repo_name+'/compare/'+data.version.hash+'...'+data.update_version.hash;
|
||||
var changelog = 'https://github.com/'+data.repo_name+'/compare/'+data.version.hash+'...'+data.branch;
|
||||
if(data.update_version.changelog)
|
||||
changelog = data.update_version.changelog + '#' + data.version.hash+'...'+data.update_version.hash
|
||||
|
||||
@@ -81,13 +89,19 @@ var UpdaterBase = new Class({
|
||||
Api.request('updater.update', {
|
||||
'onComplete': function(json){
|
||||
if(json.success){
|
||||
App.restart('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating');
|
||||
App.checkAvailable.delay(500, App);
|
||||
if(self.message)
|
||||
self.message.destroy();
|
||||
self.updating();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updating: function(){
|
||||
App.blockPage('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating');
|
||||
App.checkAvailable.delay(500, App, [1000, function(){
|
||||
window.location.reload();
|
||||
}]);
|
||||
if(self.message)
|
||||
self.message.destroy();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -53,6 +53,13 @@ def fireEvent(name, *args, **kwargs):
|
||||
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:
|
||||
@@ -129,24 +136,23 @@ def fireEvent(name, *args, **kwargs):
|
||||
if not is_after_event:
|
||||
fireEvent('%s.after' % name, is_after_event = True)
|
||||
|
||||
if on_complete:
|
||||
on_complete()
|
||||
|
||||
return results
|
||||
except KeyError, e:
|
||||
pass
|
||||
except Exception:
|
||||
log.error('%s: %s' % (name, traceback.format_exc()))
|
||||
|
||||
def fireEventAsync(name, *args, **kwargs):
|
||||
#log.debug('Async "%s": %s, %s' % (name, args, kwargs))
|
||||
def fireEventAsync(*args, **kwargs):
|
||||
try:
|
||||
e = events[name]
|
||||
e.lock.acquire()
|
||||
e.asynchronous = True
|
||||
e.error_handler = errorHandler
|
||||
e(*args, **kwargs)
|
||||
e.lock.release()
|
||||
my_thread = threading.Thread(target = fireEvent, args = args, kwargs = kwargs)
|
||||
my_thread.setDaemon(True)
|
||||
my_thread.start()
|
||||
return True
|
||||
except Exception, e:
|
||||
log.error('%s: %s' % (name, e))
|
||||
log.error('%s: %s' % (args[0], e))
|
||||
|
||||
def errorHandler(error):
|
||||
etype, value, tb = error
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import natcmp
|
||||
from flask.globals import current_app
|
||||
from flask.helpers import json
|
||||
from flask.helpers import json, make_response
|
||||
from libs.werkzeug.urls import url_decode
|
||||
from urllib import unquote
|
||||
import flask
|
||||
@@ -70,9 +70,13 @@ def jsonify(mimetype, *args, **kwargs):
|
||||
return getattr(current_app, 'response_class')(content, mimetype = mimetype)
|
||||
|
||||
def jsonified(*args, **kwargs):
|
||||
from couchpotato.environment import Env
|
||||
callback = getParam('callback_func', None)
|
||||
if callback:
|
||||
return padded_jsonify(callback, *args, **kwargs)
|
||||
content = padded_jsonify(callback, *args, **kwargs)
|
||||
else:
|
||||
return jsonify('text/javascript' if Env.doDebug() else 'application/json', *args, **kwargs)
|
||||
content = jsonify('application/json', *args, **kwargs)
|
||||
|
||||
response = make_response(content)
|
||||
response.cache_control.no_cache = True
|
||||
|
||||
return response
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.api import addApiView, addNonBlockApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.request import jsonified, getParam
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
@@ -8,14 +8,19 @@ from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from couchpotato.core.settings.model import Notification as Notif
|
||||
from sqlalchemy.sql.expression import or_
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class CoreNotifier(Notification):
|
||||
|
||||
m_lock = threading.Lock()
|
||||
messages = []
|
||||
listeners = []
|
||||
|
||||
listen_to = [
|
||||
'movie.downloaded', 'movie.snatched',
|
||||
'updater.available', 'updater.updated',
|
||||
@@ -46,14 +51,9 @@ class CoreNotifier(Notification):
|
||||
}"""}
|
||||
})
|
||||
|
||||
addNonBlockApiView('notification.listener', (self.addListener, self.removeListener))
|
||||
addApiView('notification.listener', self.listener)
|
||||
|
||||
self.registerEvents()
|
||||
|
||||
def registerEvents(self):
|
||||
|
||||
# Library update, frontend refresh
|
||||
addEvent('library.update_finish', lambda data: fireEvent('notify.frontend', type = 'library.update', data = data))
|
||||
|
||||
def markAsRead(self):
|
||||
ids = [x.strip() for x in getParam('ids').split(',')]
|
||||
@@ -114,25 +114,85 @@ class CoreNotifier(Notification):
|
||||
ndict = n.to_dict()
|
||||
ndict['type'] = 'notification'
|
||||
ndict['time'] = time.time()
|
||||
self.messages.append(ndict)
|
||||
|
||||
self.frontend(type = listener, data = data)
|
||||
|
||||
#db.close()
|
||||
return True
|
||||
|
||||
def frontend(self, type = 'notification', data = {}):
|
||||
self.messages.append({
|
||||
|
||||
self.m_lock.acquire()
|
||||
message = {
|
||||
'message_id': str(uuid.uuid4()),
|
||||
'time': time.time(),
|
||||
'type': type,
|
||||
'data': data,
|
||||
})
|
||||
}
|
||||
self.messages.append(message)
|
||||
|
||||
while len(self.listeners) > 0 and not self.shuttingDown():
|
||||
try:
|
||||
listener, last_id = self.listeners.pop()
|
||||
listener({
|
||||
'success': True,
|
||||
'result': [message],
|
||||
})
|
||||
except:
|
||||
break
|
||||
|
||||
self.m_lock.release()
|
||||
self.cleanMessages()
|
||||
|
||||
def addListener(self, callback, last_id = None):
|
||||
|
||||
if last_id:
|
||||
messages = self.getMessages(last_id)
|
||||
if len(messages) > 0:
|
||||
return callback({
|
||||
'success': True,
|
||||
'result': messages,
|
||||
})
|
||||
|
||||
self.listeners.append((callback, last_id))
|
||||
|
||||
|
||||
def removeListener(self, callback):
|
||||
|
||||
for list_tuple in self.listeners:
|
||||
try:
|
||||
listener, last_id = list_tuple
|
||||
if listener == callback:
|
||||
self.listeners.remove(list_tuple)
|
||||
except:
|
||||
pass
|
||||
|
||||
def cleanMessages(self):
|
||||
self.m_lock.acquire()
|
||||
|
||||
for message in self.messages:
|
||||
if message['time'] < (time.time() - 15):
|
||||
self.messages.remove(message)
|
||||
|
||||
self.m_lock.release()
|
||||
|
||||
def getMessages(self, last_id):
|
||||
self.m_lock.acquire()
|
||||
|
||||
recent = []
|
||||
index = 0
|
||||
for i in xrange(len(self.messages)):
|
||||
index = len(self.messages) - i - 1
|
||||
if self.messages[index]["message_id"] == last_id: break
|
||||
recent = self.messages[index:]
|
||||
|
||||
self.m_lock.release()
|
||||
|
||||
return recent or []
|
||||
|
||||
def listener(self):
|
||||
|
||||
messages = []
|
||||
for message in self.messages:
|
||||
#delete message older then 15s
|
||||
if message['time'] > (time.time() - 15):
|
||||
messages.append(message)
|
||||
|
||||
# Get unread
|
||||
if getParam('init'):
|
||||
@@ -146,9 +206,6 @@ class CoreNotifier(Notification):
|
||||
ndict['type'] = 'notification'
|
||||
messages.append(ndict)
|
||||
|
||||
#db.close()
|
||||
|
||||
self.messages = []
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'result': messages,
|
||||
|
||||
@@ -8,8 +8,8 @@ var NotificationBase = new Class({
|
||||
self.setOptions(options);
|
||||
|
||||
// Listener
|
||||
App.addEvent('load', self.startInterval.bind(self));
|
||||
App.addEvent('unload', self.stopTimer.bind(self));
|
||||
App.addEvent('unload', self.stopPoll.bind(self));
|
||||
App.addEvent('reload', self.startInterval.bind(self, [true]));
|
||||
App.addEvent('notification', self.notify.bind(self));
|
||||
|
||||
// Add test buttons to settings page
|
||||
@@ -30,7 +30,11 @@ var NotificationBase = new Class({
|
||||
'href': App.createUrl('notifications'),
|
||||
'text': 'Show older notifications'
|
||||
})); */
|
||||
})
|
||||
});
|
||||
|
||||
window.addEvent('load', function(){
|
||||
self.startInterval()
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
@@ -83,37 +87,61 @@ var NotificationBase = new Class({
|
||||
|
||||
},
|
||||
|
||||
startInterval: function(){
|
||||
startInterval: function(force){
|
||||
var self = this;
|
||||
|
||||
if(self.stopped && !force){
|
||||
self.stopped = false;
|
||||
return;
|
||||
}
|
||||
|
||||
self.request = Api.request('notification.listener', {
|
||||
'initialDelay': 100,
|
||||
'delay': 3000,
|
||||
Api.request('notification.listener', {
|
||||
'data': {'init':true},
|
||||
'onSuccess': self.processData.bind(self)
|
||||
})
|
||||
|
||||
self.request.startTimer()
|
||||
}).send()
|
||||
|
||||
},
|
||||
|
||||
startTimer: function(){
|
||||
if(this.request)
|
||||
this.request.startTimer()
|
||||
startPoll: function(){
|
||||
var self = this;
|
||||
|
||||
if(self.stopped || (self.request && self.request.isRunning()))
|
||||
return;
|
||||
|
||||
self.request = Api.request('nonblock/notification.listener', {
|
||||
'onSuccess': self.processData.bind(self),
|
||||
'data': {
|
||||
'last_id': self.last_id
|
||||
},
|
||||
'onFailure': function(){
|
||||
self.startPoll.delay(2000, self)
|
||||
}
|
||||
}).send()
|
||||
|
||||
},
|
||||
|
||||
stopTimer: function(){
|
||||
stopPoll: function(){
|
||||
if(this.request)
|
||||
this.request.stopTimer()
|
||||
this.request.cancel()
|
||||
this.stopped = true;
|
||||
},
|
||||
|
||||
processData: function(json){
|
||||
var self = this;
|
||||
|
||||
self.request.options.data = {}
|
||||
Array.each(json.result, function(result){
|
||||
App.fireEvent(result.type, result)
|
||||
})
|
||||
|
||||
// Process data
|
||||
if(json){
|
||||
Array.each(json.result, function(result){
|
||||
App.fireEvent(result.type, result)
|
||||
})
|
||||
|
||||
if(json.result.length > 0)
|
||||
self.last_id = json.result.getLast().message_id
|
||||
}
|
||||
|
||||
// Restart poll
|
||||
self.startPoll()
|
||||
},
|
||||
|
||||
addTestButtons: function(){
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.event import fireEvent, addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from couchpotato.environment import Env
|
||||
from gntp import notifier
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -17,7 +16,7 @@ class Growl(Notification):
|
||||
super(Growl, self).__init__()
|
||||
|
||||
if self.isEnabled():
|
||||
self.register()
|
||||
addEvent('app.load', self.register)
|
||||
|
||||
def register(self):
|
||||
if self.registered: return
|
||||
|
||||
@@ -62,13 +62,15 @@ class FileBrowser(Plugin):
|
||||
|
||||
def view(self):
|
||||
|
||||
path = getParam('path', '/')
|
||||
|
||||
try:
|
||||
dirs = self.getDirectories(path = getParam('path', '/'), show_hidden = getParam('show_hidden', True))
|
||||
dirs = self.getDirectories(path = path, show_hidden = getParam('show_hidden', True))
|
||||
except:
|
||||
dirs = []
|
||||
|
||||
return jsonified({
|
||||
'is_root': getParam('path', '/') == '/',
|
||||
'is_root': path == '/' or not path,
|
||||
'empty': len(dirs) == 0,
|
||||
'dirs': dirs,
|
||||
})
|
||||
|
||||
@@ -127,9 +127,6 @@ class LibraryPlugin(Plugin):
|
||||
|
||||
library_dict = library.to_dict(self.default_dict)
|
||||
|
||||
fireEvent('library.update_finish', data = library_dict)
|
||||
|
||||
#db.close()
|
||||
return library_dict
|
||||
|
||||
def updateReleaseDate(self, identifier):
|
||||
@@ -138,8 +135,8 @@ class LibraryPlugin(Plugin):
|
||||
library = db.query(Library).filter_by(identifier = identifier).first()
|
||||
|
||||
if not library.info:
|
||||
self.update(identifier)
|
||||
dates = library.get('info', {}).get('release_dates')
|
||||
library_dict = self.update(identifier)
|
||||
dates = library_dict.get('info', {}).get('release_dates')
|
||||
else:
|
||||
dates = library.info.get('release_date')
|
||||
|
||||
|
||||
@@ -239,16 +239,18 @@ class MoviePlugin(Plugin):
|
||||
db = get_session()
|
||||
|
||||
for id in getParam('id').split(','):
|
||||
fireEvent('notify.frontend', type = 'movie.busy.%s' % id, data = True)
|
||||
movie = db.query(Movie).filter_by(id = id).first()
|
||||
|
||||
# Get current selected title
|
||||
default_title = ''
|
||||
for title in movie.library.titles:
|
||||
if title.default: default_title = title.title
|
||||
|
||||
if movie:
|
||||
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
|
||||
fireEventAsync('searcher.single', movie.to_dict(self.default_dict))
|
||||
|
||||
# Get current selected title
|
||||
default_title = ''
|
||||
for title in movie.library.titles:
|
||||
if title.default: default_title = title.title
|
||||
|
||||
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(id))
|
||||
|
||||
|
||||
#db.close()
|
||||
return jsonified({
|
||||
@@ -287,6 +289,7 @@ class MoviePlugin(Plugin):
|
||||
|
||||
db = get_session()
|
||||
m = db.query(Movie).filter_by(library_id = library.get('id')).first()
|
||||
added = True
|
||||
do_search = False
|
||||
if not m:
|
||||
m = Movie(
|
||||
@@ -295,8 +298,14 @@ class MoviePlugin(Plugin):
|
||||
status_id = status_active.get('id'),
|
||||
)
|
||||
db.add(m)
|
||||
fireEvent('library.update', params.get('identifier'), default_title = params.get('title', ''))
|
||||
do_search = True
|
||||
db.commit()
|
||||
|
||||
onComplete = None
|
||||
if search_after:
|
||||
onComplete = self.createOnComplete(m.id)
|
||||
|
||||
fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
|
||||
search_after = False
|
||||
elif force_readd:
|
||||
# Clean snatched history
|
||||
for release in m.releases:
|
||||
@@ -306,9 +315,11 @@ class MoviePlugin(Plugin):
|
||||
m.profile_id = params.get('profile_id', default_profile.get('id'))
|
||||
else:
|
||||
log.debug('Movie already exists, not updating: %s' % params)
|
||||
added = False
|
||||
|
||||
if force_readd:
|
||||
m.status_id = status_active.get('id')
|
||||
do_search = True
|
||||
|
||||
db.commit()
|
||||
|
||||
@@ -321,8 +332,12 @@ class MoviePlugin(Plugin):
|
||||
|
||||
movie_dict = m.to_dict(self.default_dict)
|
||||
|
||||
if (force_readd or do_search) and search_after:
|
||||
fireEventAsync('searcher.single', movie_dict)
|
||||
if do_search and search_after:
|
||||
onComplete = self.createOnComplete(m.id)
|
||||
onComplete()
|
||||
|
||||
if added:
|
||||
fireEvent('notify.frontend', type = 'movie.added', data = movie_dict)
|
||||
|
||||
#db.close()
|
||||
return movie_dict
|
||||
@@ -369,7 +384,7 @@ class MoviePlugin(Plugin):
|
||||
fireEvent('movie.restatus', m.id)
|
||||
|
||||
movie_dict = m.to_dict(self.default_dict)
|
||||
fireEventAsync('searcher.single', movie_dict)
|
||||
fireEventAsync('searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id))
|
||||
|
||||
#db.close()
|
||||
return jsonified({
|
||||
@@ -458,3 +473,22 @@ class MoviePlugin(Plugin):
|
||||
#db.close()
|
||||
|
||||
return True
|
||||
|
||||
def createOnComplete(self, movie_id):
|
||||
|
||||
def onComplete():
|
||||
db = get_session()
|
||||
movie = db.query(Movie).filter_by(id = movie_id).first()
|
||||
fireEventAsync('searcher.single', movie.to_dict(self.default_dict), on_complete = self.createNotifyFront(movie_id))
|
||||
|
||||
return onComplete
|
||||
|
||||
|
||||
def createNotifyFront(self, movie_id):
|
||||
|
||||
def notifyFront():
|
||||
db = get_session()
|
||||
movie = db.query(Movie).filter_by(id = movie_id).first()
|
||||
fireEvent('notify.frontend', type = 'movie.update.%s' % movie.id, data = movie.to_dict(self.default_dict))
|
||||
|
||||
return notifyFront
|
||||
|
||||
@@ -5,10 +5,12 @@ var MovieList = new Class({
|
||||
options: {
|
||||
navigation: true,
|
||||
limit: 50,
|
||||
menu: []
|
||||
menu: [],
|
||||
add_new: false
|
||||
},
|
||||
|
||||
movies: [],
|
||||
movies_added: {},
|
||||
letters: {},
|
||||
filter: {
|
||||
'startswith': null,
|
||||
@@ -30,6 +32,17 @@ var MovieList = new Class({
|
||||
})
|
||||
);
|
||||
self.getMovies();
|
||||
|
||||
if(options.add_new)
|
||||
App.addEvent('movie.added', self.movieAdded.bind(self))
|
||||
},
|
||||
|
||||
movieAdded: function(notification){
|
||||
var self = this;
|
||||
window.scroll(0,0);
|
||||
|
||||
if(!self.movies_added[notification.data.id])
|
||||
self.createMovie(notification.data, 'top');
|
||||
},
|
||||
|
||||
create: function(){
|
||||
@@ -71,26 +84,31 @@ var MovieList = new Class({
|
||||
}
|
||||
|
||||
Object.each(movies, function(movie){
|
||||
|
||||
// Attach proper actions
|
||||
var a = self.options.actions,
|
||||
status = Status.get(movie.status_id);
|
||||
var actions = a[status.identifier.capitalize()] || a.Wanted || {};
|
||||
|
||||
var m = new Movie(self, {
|
||||
'actions': actions,
|
||||
'view': self.current_view,
|
||||
'onSelect': self.calculateSelected.bind(self)
|
||||
}, movie);
|
||||
$(m).inject(self.movie_list);
|
||||
m.fireEvent('injected');
|
||||
|
||||
self.movies.include(m)
|
||||
|
||||
self.createMovie(movie);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
createMovie: function(movie, inject_at){
|
||||
var self = this;
|
||||
|
||||
// Attach proper actions
|
||||
var a = self.options.actions,
|
||||
status = Status.get(movie.status_id);
|
||||
var actions = a[status.identifier.capitalize()] || a.Wanted || {};
|
||||
|
||||
var m = new Movie(self, {
|
||||
'actions': actions,
|
||||
'view': self.current_view,
|
||||
'onSelect': self.calculateSelected.bind(self)
|
||||
}, movie);
|
||||
$(m).inject(self.movie_list, inject_at || 'bottom');
|
||||
m.fireEvent('injected');
|
||||
|
||||
self.movies.include(m)
|
||||
self.movies_added[movie.id] = true;
|
||||
},
|
||||
|
||||
createNavigation: function(){
|
||||
var self = this;
|
||||
var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
@@ -260,6 +278,7 @@ var MovieList = new Class({
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).preventDefault();
|
||||
this.set('text', 'Deleting..')
|
||||
Api.request('movie.delete', {
|
||||
'data': {
|
||||
'id': ids.join(','),
|
||||
@@ -268,14 +287,19 @@ var MovieList = new Class({
|
||||
'onSuccess': function(){
|
||||
qObj.close();
|
||||
|
||||
var erase_movies = [];
|
||||
self.movies.each(function(movie){
|
||||
if (movie.isSelected()){
|
||||
$(movie).destroy()
|
||||
self.movies.erase(movie)
|
||||
erase_movies.include(movie)
|
||||
}
|
||||
});
|
||||
|
||||
self.calculateSelected()
|
||||
erase_movies.each(function(movie){
|
||||
self.movies.erase(movie);
|
||||
});
|
||||
|
||||
self.calculateSelected();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
text-overflow: ellipsis;
|
||||
width: 64%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.movies .info .year {
|
||||
@@ -152,7 +152,6 @@
|
||||
.movies .list_view .data .quality, .movies .mass_edit_view .data .quality {
|
||||
text-align: right;
|
||||
float: right;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.movies .data .quality .available, .movies .data .quality .snatched {
|
||||
@@ -200,6 +199,7 @@
|
||||
.movies .list_view .data:hover .actions, .movies .mass_edit_view .data:hover .actions {
|
||||
margin: -34px 2px 0 0;
|
||||
background: #4e5969;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.movies .delete_container {
|
||||
|
||||
@@ -11,53 +11,121 @@ var Movie = new Class({
|
||||
self.view = options.view || 'thumbs';
|
||||
self.list = list;
|
||||
|
||||
self.el = new Element('div.movie.inlay');
|
||||
|
||||
self.profile = Quality.getProfile(data.profile_id) || {};
|
||||
self.parent(self, options);
|
||||
|
||||
App.addEvent('movie.update.'+data.id, self.update.bind(self));
|
||||
App.addEvent('movie.busy.'+data.id, function(notification){
|
||||
if(notification.data)
|
||||
self.busy(true)
|
||||
});
|
||||
},
|
||||
|
||||
busy: function(set_busy){
|
||||
var self = this;
|
||||
|
||||
if(!set_busy){
|
||||
if(self.spinner){
|
||||
self.mask.fade('out');
|
||||
setTimeout(function(){
|
||||
if(self.mask)
|
||||
self.mask.destroy();
|
||||
if(self.spinner)
|
||||
self.spinner.el.destroy();
|
||||
self.spinner = null;
|
||||
self.mask = null;
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
else if(!self.spinner) {
|
||||
self.createMask();
|
||||
self.spinner = createSpinner(self.mask);
|
||||
self.positionMask();
|
||||
self.mask.fade('in');
|
||||
}
|
||||
},
|
||||
|
||||
createMask: function(){
|
||||
var self = this;
|
||||
self.mask = new Element('div.mask', {
|
||||
'styles': {
|
||||
'z-index': '1'
|
||||
}
|
||||
}).inject(self.el, 'top').fade('hide');
|
||||
self.positionMask();
|
||||
},
|
||||
|
||||
positionMask: function(){
|
||||
var self = this,
|
||||
s = self.el.getSize()
|
||||
|
||||
return self.mask.setStyles({
|
||||
'width': s.x,
|
||||
'height': s.y
|
||||
}).position({
|
||||
'relativeTo': self.el
|
||||
})
|
||||
},
|
||||
|
||||
update: function(notification){
|
||||
var self = this;
|
||||
|
||||
self.data = notification.data;
|
||||
self.container.destroy();
|
||||
|
||||
self.profile = Quality.getProfile(self.data.profile_id) || {};
|
||||
self.create();
|
||||
|
||||
self.busy(false);
|
||||
},
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
self.el = new Element('div.movie.inlay').adopt(
|
||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||
'events': {
|
||||
'change': function(){
|
||||
self.fireEvent('select')
|
||||
}
|
||||
}
|
||||
}),
|
||||
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
||||
self.data_container = new Element('div.data.inlay.light', {
|
||||
'tween': {
|
||||
duration: 400,
|
||||
transition: 'quint:in:out',
|
||||
onComplete: self.fireEvent.bind(self, 'slideEnd')
|
||||
}
|
||||
}).adopt(
|
||||
self.info_container = new Element('div.info').adopt(
|
||||
self.title = new Element('div.title', {
|
||||
'text': self.getTitle() || 'n/a'
|
||||
}),
|
||||
self.year = new Element('div.year', {
|
||||
'text': self.data.library.year || 'n/a'
|
||||
}),
|
||||
self.rating = new Element('div.rating.icon', {
|
||||
'text': self.data.library.rating
|
||||
}),
|
||||
self.description = new Element('div.description', {
|
||||
'text': self.data.library.plot
|
||||
}),
|
||||
self.quality = new Element('div.quality', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
var releases = self.el.getElement('.actions .releases');
|
||||
if(releases)
|
||||
releases.fireEvent('click', [e])
|
||||
}
|
||||
self.el.adopt(
|
||||
self.container = new Element('div.movie_container').adopt(
|
||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||
'events': {
|
||||
'change': function(){
|
||||
self.fireEvent('select')
|
||||
}
|
||||
})
|
||||
),
|
||||
self.actions = new Element('div.actions')
|
||||
}
|
||||
}),
|
||||
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
||||
self.data_container = new Element('div.data.inlay.light', {
|
||||
'tween': {
|
||||
duration: 400,
|
||||
transition: 'quint:in:out',
|
||||
onComplete: self.fireEvent.bind(self, 'slideEnd')
|
||||
}
|
||||
}).adopt(
|
||||
self.info_container = new Element('div.info').adopt(
|
||||
self.title = new Element('div.title', {
|
||||
'text': self.getTitle() || 'n/a'
|
||||
}),
|
||||
self.year = new Element('div.year', {
|
||||
'text': self.data.library.year || 'n/a'
|
||||
}),
|
||||
self.rating = new Element('div.rating.icon', {
|
||||
'text': self.data.library.rating
|
||||
}),
|
||||
self.description = new Element('div.description', {
|
||||
'text': self.data.library.plot
|
||||
}),
|
||||
self.quality = new Element('div.quality', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
var releases = self.el.getElement('.actions .releases');
|
||||
if(releases)
|
||||
releases.fireEvent('click', [e])
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
self.actions = new Element('div.actions')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -150,7 +218,7 @@ var Movie = new Class({
|
||||
self.el.removeEvents('outerClick')
|
||||
|
||||
self.addEvent('slideEnd:once', function(){
|
||||
self.el.getElements('> :not(.data):not(.poster)').hide();
|
||||
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
|
||||
});
|
||||
|
||||
self.data_container.tween('right', -840, 0);
|
||||
|
||||
@@ -221,7 +221,9 @@ Block.Search.Item = new Class({
|
||||
}
|
||||
}).adopt(
|
||||
self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
||||
'src': info.images.poster[0]
|
||||
'src': info.images.poster[0],
|
||||
'height': null,
|
||||
'width': null
|
||||
}) : null,
|
||||
new Element('div.info').adopt(
|
||||
self.title = new Element('h2', {
|
||||
@@ -332,8 +334,10 @@ Block.Search.Item = new Class({
|
||||
|
||||
self.options.adopt(
|
||||
new Element('div').adopt(
|
||||
self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
||||
'src': self.info.images.poster[0]
|
||||
self.option_thumbnail = self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
||||
'src': self.info.images.poster[0],
|
||||
'height': null,
|
||||
'width': null
|
||||
}) : null,
|
||||
self.info.in_wanted ? new Element('span.in_wanted', {
|
||||
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
|
||||
|
||||
@@ -17,9 +17,11 @@ rename_options = {
|
||||
'audio': 'Audio (DTS)',
|
||||
'group': 'Releasegroup name',
|
||||
'source': 'Source media (Bluray)',
|
||||
'filename': 'Original filename',
|
||||
'original': 'Original filename',
|
||||
'original_folder': 'Original foldername',
|
||||
'imdb_id': 'IMDB id (tt0123456)',
|
||||
'cd': 'CD number (cd1)',
|
||||
'cd_nr': 'Just the cd nr. (1)',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ class Renamer(Plugin):
|
||||
if file_type is 'subtitle':
|
||||
|
||||
# rename subtitles with or without language
|
||||
#rename_files[current_file] = os.path.join(destination, final_folder_name, final_file_name)
|
||||
rename_files[current_file] = os.path.join(destination, final_folder_name, final_file_name)
|
||||
sub_langs = group['subtitle_language'].get(current_file, [])
|
||||
|
||||
rename_extras = self.getRenameExtras(
|
||||
@@ -314,7 +314,6 @@ class Renamer(Plugin):
|
||||
|
||||
break
|
||||
elif release.status_id is snatched_status.get('id'):
|
||||
print release.quality.label, group['meta_data']['quality']['label']
|
||||
if release.quality.id is group['meta_data']['quality']['id']:
|
||||
log.debug('Marking release as downloaded')
|
||||
release.status_id = downloaded_status.get('id')
|
||||
|
||||
@@ -4,7 +4,7 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
||||
from couchpotato.core.helpers.variable import getExt, getImdb, tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import File
|
||||
from couchpotato.core.settings.model import File, Movie
|
||||
from couchpotato.environment import Env
|
||||
from enzyme.exceptions import NoParserError, ParseError
|
||||
from guessit import guess_movie_info
|
||||
@@ -27,7 +27,7 @@ class Scanner(Plugin):
|
||||
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
|
||||
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
|
||||
extensions = {
|
||||
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts'],
|
||||
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],
|
||||
'movie_extra': ['mds'],
|
||||
'dvd': ['vts_*', 'vob'],
|
||||
'nfo': ['nfo', 'txt', 'tag'],
|
||||
@@ -161,6 +161,8 @@ class Scanner(Plugin):
|
||||
except:
|
||||
log.error('Failed getting files from %s: %s' % (folder, traceback.format_exc()))
|
||||
|
||||
db = get_session()
|
||||
|
||||
for file_path in files:
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
@@ -237,19 +239,51 @@ class Scanner(Plugin):
|
||||
|
||||
|
||||
# Group the files based on the identifier
|
||||
for identifier, group in movie_files.iteritems():
|
||||
delete_identifiers = []
|
||||
for identifier, found_files in self.path_identifiers.iteritems():
|
||||
log.debug('Grouping files on identifier: %s' % identifier)
|
||||
|
||||
found_files = set(self.path_identifiers.get(identifier, []))
|
||||
group['unsorted_files'].extend(found_files)
|
||||
group = movie_files.get(identifier)
|
||||
if group:
|
||||
group['unsorted_files'].extend(found_files)
|
||||
delete_identifiers.append(identifier)
|
||||
|
||||
# Remove the found files from the leftover stack
|
||||
leftovers = leftovers - found_files
|
||||
# Remove the found files from the leftover stack
|
||||
leftovers = leftovers - set(found_files)
|
||||
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
break
|
||||
|
||||
# Cleaning up used
|
||||
for identifier in delete_identifiers:
|
||||
del self.path_identifiers[identifier]
|
||||
del delete_identifiers
|
||||
|
||||
# Group based on folder
|
||||
delete_identifiers = []
|
||||
for identifier, found_files in self.path_identifiers.iteritems():
|
||||
log.debug('Grouping files on foldername: %s' % identifier)
|
||||
|
||||
for ff in found_files:
|
||||
new_identifier = self.createStringIdentifier(os.path.dirname(ff), folder)
|
||||
|
||||
group = movie_files.get(new_identifier)
|
||||
if group:
|
||||
group['unsorted_files'].extend([ff])
|
||||
delete_identifiers.append(identifier)
|
||||
|
||||
# Remove the found files from the leftover stack
|
||||
leftovers = leftovers - set([ff])
|
||||
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
break
|
||||
|
||||
# Cleaning up used
|
||||
for identifier in delete_identifiers:
|
||||
del self.path_identifiers[identifier]
|
||||
del delete_identifiers
|
||||
|
||||
# Determine file types
|
||||
processed_movies = {}
|
||||
@@ -327,6 +361,10 @@ class Scanner(Plugin):
|
||||
group['library'] = self.determineMovie(group)
|
||||
if not group['library']:
|
||||
log.error('Unable to determine movie: %s' % group['identifiers'])
|
||||
else:
|
||||
movie = db.query(Movie).filter_by(library_id = group['library']['id']).first()
|
||||
group['movie_id'] = None if not movie else movie.id
|
||||
|
||||
|
||||
processed_movies[identifier] = group
|
||||
|
||||
@@ -647,7 +685,7 @@ class Scanner(Plugin):
|
||||
identifier = self.removeCPTag(identifier)
|
||||
|
||||
# groups, release tags, scenename cleaner, regex isn't correct
|
||||
identifier = re.sub(self.clean, '::', simplifyString(identifier))
|
||||
identifier = re.sub(self.clean, '::', simplifyString(identifier)).strip(':')
|
||||
|
||||
# Year
|
||||
year = self.findYear(identifier)
|
||||
|
||||
@@ -29,7 +29,7 @@ config = [{
|
||||
{
|
||||
'name': 'ignored_words',
|
||||
'label': 'Ignored words',
|
||||
'default': 'german, dutch, french, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub',
|
||||
'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub',
|
||||
},
|
||||
],
|
||||
}, {
|
||||
|
||||
@@ -83,6 +83,9 @@ class Searcher(Plugin):
|
||||
if not default_title:
|
||||
return
|
||||
|
||||
fireEvent('notify.frontend', type = 'searcher.started.%s' % movie['id'], data = True)
|
||||
|
||||
ret = False
|
||||
for quality_type in movie['profile']['types']:
|
||||
if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases):
|
||||
log.info('To early to search for %s, %s' % (quality_type['quality']['identifier'], default_title))
|
||||
@@ -107,7 +110,7 @@ class Searcher(Plugin):
|
||||
|
||||
# Check if movie isn't deleted while searching
|
||||
if not db.query(Movie).filter_by(id = movie.get('id')).first():
|
||||
return
|
||||
break
|
||||
|
||||
# Add them to this movie releases list
|
||||
for nzb in sorted_results:
|
||||
@@ -144,7 +147,8 @@ class Searcher(Plugin):
|
||||
for nzb in sorted_results:
|
||||
downloaded = self.download(data = nzb, movie = movie)
|
||||
if downloaded is True:
|
||||
return True
|
||||
ret = True
|
||||
break
|
||||
elif downloaded != 'try_next':
|
||||
break
|
||||
else:
|
||||
@@ -153,11 +157,13 @@ class Searcher(Plugin):
|
||||
break
|
||||
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
if self.shuttingDown() or ret:
|
||||
break
|
||||
|
||||
fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True)
|
||||
|
||||
#db.close()
|
||||
return False
|
||||
return ret
|
||||
|
||||
def download(self, data, movie, manual = False):
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.variable import getExt
|
||||
from couchpotato.core.helpers.variable import getExt, getTitle
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
import os
|
||||
@@ -17,6 +17,9 @@ class Trailer(Plugin):
|
||||
if self.isDisabled() or len(group['files']['trailer']) > 0: return
|
||||
|
||||
trailers = fireEvent('trailer.search', group = group, merge = True)
|
||||
if not trailers or trailers == []:
|
||||
log.info('No trailers found for: %s' % getTitle(group['library']))
|
||||
return
|
||||
|
||||
for trailer in trailers.get(self.conf('quality'), []):
|
||||
destination = '%s-trailer.%s' % (self.getRootName(group), getExt(trailer))
|
||||
|
||||
@@ -10,7 +10,7 @@ config = [{
|
||||
'tab': 'automation',
|
||||
'name': 'imdb_automation',
|
||||
'label': 'IMDB',
|
||||
'description': 'From any <strong>public</strong> IMDB watchlists',
|
||||
'description': 'From any <strong>public</strong> IMDB watchlists. Url should be the RSS link.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'automation_enabled',
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
from couchpotato.core.helpers.variable import md5
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.helpers.variable import md5, getImdb
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
from couchpotato.environment import Env
|
||||
from dateutil.parser import parse
|
||||
import StringIO
|
||||
import csv
|
||||
import time
|
||||
import traceback
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class IMDB(Automation):
|
||||
class IMDB(Automation, RSS):
|
||||
|
||||
interval = 1800
|
||||
|
||||
@@ -21,34 +21,41 @@ class IMDB(Automation):
|
||||
return
|
||||
|
||||
movies = []
|
||||
headers = {}
|
||||
|
||||
for csv_url in self.conf('automation_urls').split(','):
|
||||
prop_name = 'automation.imdb.last_update.%s' % md5(csv_url)
|
||||
enablers = self.conf('automation_urls_use').split(',')
|
||||
|
||||
index = -1
|
||||
for rss_url in self.conf('automation_urls').split(','):
|
||||
|
||||
index += 1
|
||||
if not enablers[index]:
|
||||
continue
|
||||
elif 'rss.imdb' not in rss_url:
|
||||
log.error('This isn\'t the correct url.: %s' % rss_url)
|
||||
continue
|
||||
|
||||
prop_name = 'automation.imdb.last_update.%s' % md5(rss_url)
|
||||
last_update = float(Env.prop(prop_name, default = 0))
|
||||
|
||||
try:
|
||||
cache_key = 'imdb_csv.%s' % md5(csv_url)
|
||||
csv_data = self.getCache(cache_key, csv_url)
|
||||
csv_reader = csv.reader(StringIO.StringIO(csv_data))
|
||||
if not headers:
|
||||
nr = 0
|
||||
for column in csv_reader.next():
|
||||
headers[column] = nr
|
||||
nr += 1
|
||||
cache_key = 'imdb.rss.%s' % md5(rss_url)
|
||||
|
||||
for row in csv_reader:
|
||||
created = int(time.mktime(parse(row[headers['created']]).timetuple()))
|
||||
if created < last_update:
|
||||
rss_data = self.getCache(cache_key, rss_url)
|
||||
data = XMLTree.fromstring(rss_data)
|
||||
rss_movies = self.getElements(data, 'channel/item')
|
||||
|
||||
for movie in rss_movies:
|
||||
created = int(time.mktime(parse(self.getTextElement(movie, "pubDate")).timetuple()))
|
||||
imdb = getImdb(self.getTextElement(movie, "link"))
|
||||
|
||||
if not imdb or created < last_update:
|
||||
continue
|
||||
|
||||
imdb = row[headers['const']]
|
||||
if imdb:
|
||||
movies.append(imdb)
|
||||
movies.append(imdb)
|
||||
|
||||
except:
|
||||
log.error('Failed loading IMDB watchlist: %s %s' % (csv_url, traceback.format_exc()))
|
||||
log.error('Failed loading IMDB watchlist: %s %s' % (rss_url, traceback.format_exc()))
|
||||
|
||||
Env.prop(prop_name, time.time())
|
||||
|
||||
|
||||
return movies
|
||||
|
||||
@@ -151,7 +151,7 @@ class TheMovieDb(MovieProvider):
|
||||
|
||||
movie_data = {
|
||||
'via_tmdb': True,
|
||||
'id': int(movie.get('id', 0)),
|
||||
'tmdb_id': int(movie.get('id', 0)),
|
||||
'titles': [toUnicode(movie.get('name'))],
|
||||
'original_title': movie.get('original_name'),
|
||||
'images': {
|
||||
|
||||
@@ -81,6 +81,7 @@ class Mysterbin(NZBProvider):
|
||||
'size': size,
|
||||
'url': self.urls['download'] % myster_id,
|
||||
'description': description,
|
||||
'download': self.download,
|
||||
'check_nzb': False,
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ class Newzbin(NZBProvider, RSS):
|
||||
|
||||
url = "%s?%s" % (self.urls['search'], arguments)
|
||||
cache_key = str('newzbin.%s.%s.%s' % (movie['library']['identifier'], str(format_id), str(cat_id)))
|
||||
single_cat = True
|
||||
|
||||
data = self.getCache(cache_key)
|
||||
if not data:
|
||||
@@ -118,7 +117,7 @@ class Newzbin(NZBProvider, RSS):
|
||||
|
||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||
nzb = new, movie = movie, quality = quality,
|
||||
imdb_results = True, single_category = single_cat, single = True)
|
||||
imdb_results = True, single = True)
|
||||
if is_correct_movie:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
results.append(new)
|
||||
|
||||
@@ -71,6 +71,7 @@ class NzbIndex(NZBProvider, RSS):
|
||||
'id': nzbindex_id,
|
||||
'type': 'nzb',
|
||||
'provider': self.getName(),
|
||||
'download': self.download,
|
||||
'name': self.getTextElement(nzb, "title"),
|
||||
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
|
||||
'size': tryInt(enclosure['length']) / 1024 / 1024,
|
||||
|
||||
@@ -23,7 +23,6 @@ class Env(object):
|
||||
_deamonize = False
|
||||
_desktop = None
|
||||
_session = None
|
||||
_httpserver = None
|
||||
|
||||
''' Data paths and directories '''
|
||||
_app_dir = ""
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from argparse import ArgumentParser
|
||||
from couchpotato import web
|
||||
from couchpotato.api import api
|
||||
from couchpotato.api import api, NonBlockHandler
|
||||
from couchpotato.core.event import fireEventAsync, fireEvent
|
||||
from couchpotato.core.helpers.variable import getDataDir, tryInt
|
||||
from logging import handlers
|
||||
from tornado import autoreload
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import RequestHandler
|
||||
from tornado.web import RequestHandler, Application, FallbackHandler
|
||||
from tornado.wsgi import WSGIContainer
|
||||
from werkzeug.contrib.cache import FileSystemCache
|
||||
import locale
|
||||
@@ -227,20 +227,22 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
# Go go go!
|
||||
web_container = WSGIContainer(app)
|
||||
web_container._log = _log
|
||||
http_server = HTTPServer(web_container, no_keep_alive = True)
|
||||
Env.set('httpserver', http_server)
|
||||
loop = IOLoop.instance()
|
||||
|
||||
application = Application([
|
||||
(r'%s/api/%s/nonblock/(.*)/' % (url_base, api_key), NonBlockHandler),
|
||||
(r'.*', FallbackHandler, dict(fallback = web_container)),
|
||||
],
|
||||
log_function = lambda x : None,
|
||||
debug = config['use_reloader']
|
||||
)
|
||||
|
||||
try_restart = True
|
||||
restart_tries = 5
|
||||
|
||||
while try_restart:
|
||||
try:
|
||||
http_server.listen(config['port'], config['host'])
|
||||
|
||||
if config['use_reloader']:
|
||||
autoreload.start(loop)
|
||||
|
||||
application.listen(config['port'], config['host'], no_keep_alive = True)
|
||||
loop.start()
|
||||
except Exception, e:
|
||||
try:
|
||||
|
||||
BIN
couchpotato/static/images/imdb_watchlist.png
Normal file
BIN
couchpotato/static/images/imdb_watchlist.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -24,8 +24,8 @@ var CouchPotato = new Class({
|
||||
|
||||
if(window.location.hash)
|
||||
History.handleInitialState();
|
||||
else
|
||||
self.openPage(window.location.pathname);
|
||||
|
||||
self.openPage(window.location.pathname);
|
||||
|
||||
History.addEvent('change', self.openPage.bind(self));
|
||||
self.c.addEvent('click:relay(a[href^=/]:not([target]))', self.pushState.bind(self));
|
||||
@@ -211,27 +211,30 @@ var CouchPotato = new Class({
|
||||
}]);
|
||||
},
|
||||
|
||||
checkForUpdate: function(func){
|
||||
checkForUpdate: function(onComplete){
|
||||
var self = this;
|
||||
|
||||
Updater.check(func)
|
||||
Updater.check(onComplete)
|
||||
|
||||
self.blockPage('Please wait. If this takes to long, something must have gone wrong.', 'Checking for updates');
|
||||
self.checkAvailable(3000);
|
||||
},
|
||||
|
||||
checkAvailable: function(delay){
|
||||
checkAvailable: function(delay, onAvailable){
|
||||
var self = this;
|
||||
|
||||
(function(){
|
||||
|
||||
Api.request('app.available', {
|
||||
'onFailure': function(){
|
||||
self.checkAvailable.delay(1000, self);
|
||||
self.checkAvailable.delay(1000, self, [delay, onAvailable]);
|
||||
self.fireEvent('unload');
|
||||
},
|
||||
'onSuccess': function(){
|
||||
if(onAvailable)
|
||||
onAvailable()
|
||||
self.unBlockPage();
|
||||
self.fireEvent('reload');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -241,6 +244,8 @@ var CouchPotato = new Class({
|
||||
blockPage: function(message, title){
|
||||
var self = this;
|
||||
|
||||
self.unBlockPage();
|
||||
|
||||
var body = $(document.body);
|
||||
self.mask = new Element('div.mask').adopt(
|
||||
new Element('div').adopt(
|
||||
@@ -256,20 +261,14 @@ var CouchPotato = new Class({
|
||||
|
||||
unBlockPage: function(){
|
||||
var self = this;
|
||||
self.mask.get('tween').start('opacity', 0).chain(function(){
|
||||
this.element.destroy()
|
||||
});
|
||||
if(self.mask)
|
||||
self.mask.get('tween').start('opacity', 0).chain(function(){
|
||||
this.element.destroy()
|
||||
});
|
||||
},
|
||||
|
||||
createUrl: function(action, params){
|
||||
return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '')
|
||||
},
|
||||
|
||||
notify: function(options){
|
||||
return this.growl.notify({
|
||||
title: "this scrolls away",
|
||||
text: "test - hello there. mouseover to pause away action"
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ var AboutSettingTab = new Class({
|
||||
'text': 'Getting version...',
|
||||
'events': {
|
||||
'click': App.checkForUpdate.bind(App, function(json){
|
||||
self.fillVersion(json)
|
||||
self.fillVersion(json.info)
|
||||
}),
|
||||
'mouseenter': function(){
|
||||
this.set('text', 'Check for updates')
|
||||
|
||||
@@ -702,7 +702,7 @@ Option.Directory = new Class({
|
||||
var v = self.input.get('text');
|
||||
var previous_dir = self.getParentDir();
|
||||
|
||||
if(previous_dir != v && previous_dir.length > 1){
|
||||
if(previous_dir != v && previous_dir.length >= 1 && !json.is_root){
|
||||
self.back_button.set('data-value', previous_dir)
|
||||
self.back_button.set('html', '« '+self.getCurrentDirname(previous_dir))
|
||||
self.back_button.show()
|
||||
@@ -909,6 +909,7 @@ Option.Choice = new Class({
|
||||
var input = self.tag_input.getElement('li:last-child input');
|
||||
input.fireEvent('focus');
|
||||
input.focus();
|
||||
input.setCaretPosition(input.get('value').length);
|
||||
}
|
||||
|
||||
self.el.addEvent('outerClick', function(){
|
||||
@@ -965,6 +966,12 @@ Option.Choice = new Class({
|
||||
'onChange': self.setOrder.bind(self),
|
||||
'onBlur': function(){
|
||||
self.addLastTag();
|
||||
},
|
||||
'onGoLeft': function(){
|
||||
self.goLeft(this)
|
||||
},
|
||||
'onGoRight': function(){
|
||||
self.goRight(this)
|
||||
}
|
||||
});
|
||||
$(tag).inject(self.tag_input);
|
||||
@@ -979,6 +986,30 @@ Option.Choice = new Class({
|
||||
return tag;
|
||||
},
|
||||
|
||||
goLeft: function(from_tag){
|
||||
var self = this;
|
||||
|
||||
from_tag.blur();
|
||||
|
||||
var prev_index = self.tags.indexOf(from_tag)-1;
|
||||
if(prev_index >= 0)
|
||||
self.tags[prev_index].selectFrom('right')
|
||||
else
|
||||
from_tag.focus();
|
||||
|
||||
},
|
||||
goRight: function(from_tag){
|
||||
var self = this;
|
||||
|
||||
from_tag.blur();
|
||||
|
||||
var next_index = self.tags.indexOf(from_tag)+1;
|
||||
if(next_index < self.tags.length)
|
||||
self.tags[next_index].selectFrom('left')
|
||||
else
|
||||
from_tag.focus();
|
||||
},
|
||||
|
||||
setOrder: function(){
|
||||
var self = this;
|
||||
|
||||
@@ -1059,7 +1090,16 @@ Option.Choice.Tag = new Class({
|
||||
'width': 0
|
||||
},
|
||||
'events': {
|
||||
'keyup': self.is_choice ? null : function(){
|
||||
'keyup': self.is_choice ? null : function(e){
|
||||
var current_caret_pos = self.input.getCaretPosition();
|
||||
if(e.key == 'left' && current_caret_pos == self.last_caret_pos){
|
||||
self.fireEvent('goLeft');
|
||||
}
|
||||
else if (e.key == 'right' && self.last_caret_pos === current_caret_pos){
|
||||
self.fireEvent('goRight');
|
||||
}
|
||||
self.last_caret_pos = self.input.getCaretPosition();
|
||||
|
||||
self.setWidth();
|
||||
self.fireEvent('change');
|
||||
},
|
||||
@@ -1081,8 +1121,70 @@ Option.Choice.Tag = new Class({
|
||||
|
||||
},
|
||||
|
||||
blur: function(){
|
||||
var self = this;
|
||||
|
||||
self.input.blur();
|
||||
|
||||
self.selected = false;
|
||||
self.el.removeClass('selected');
|
||||
self.input.removeEvents('outerClick');
|
||||
},
|
||||
|
||||
focus: function(){
|
||||
this.input.focus();
|
||||
var self = this;
|
||||
if(!self.is_choice){
|
||||
this.input.focus();
|
||||
}
|
||||
else {
|
||||
if(self.selected) return;
|
||||
self.selected = true;
|
||||
self.el.addClass('selected');
|
||||
self.input.addEvent('outerClick', self.blur.bind(self));
|
||||
|
||||
var temp_input = new Element('input', {
|
||||
'events': {
|
||||
'keyup': function(e){
|
||||
e.stop();
|
||||
|
||||
if(e.key == 'right'){
|
||||
self.fireEvent('goRight');
|
||||
this.destroy();
|
||||
}
|
||||
else if (e.key == 'left'){
|
||||
self.fireEvent('goLeft');
|
||||
this.destroy();
|
||||
}
|
||||
else if (e.key == 'backspace'){
|
||||
self.del();
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
},
|
||||
'styles': {
|
||||
'height': 0,
|
||||
'width': 0,
|
||||
'position': 'absolute',
|
||||
'top': -200
|
||||
}
|
||||
});
|
||||
self.el.adopt(temp_input)
|
||||
temp_input.focus();
|
||||
}
|
||||
},
|
||||
|
||||
selectFrom: function(direction){
|
||||
var self = this;
|
||||
|
||||
if(!direction || self.is_choice){
|
||||
self.focus();
|
||||
}
|
||||
else {
|
||||
self.focus();
|
||||
var position = direction == 'left' ? 0 : self.input.get('value').length;
|
||||
self.input.setCaretPosition(position);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
setWidth: function(){
|
||||
|
||||
@@ -14,10 +14,10 @@ Page.Wanted = new Class({
|
||||
self.wanted = new MovieList({
|
||||
'identifier': 'wanted',
|
||||
'status': 'active',
|
||||
'actions': MovieActions
|
||||
'actions': MovieActions,
|
||||
'add_new': true
|
||||
});
|
||||
$(self.wanted).inject(self.el);
|
||||
App.addEvent('library.update', self.wanted.update.bind(self.wanted));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,14 +73,20 @@ window.addEvent('domready', function(){
|
||||
new Element('option', {
|
||||
'text': alt.title
|
||||
}).inject(self.title_select);
|
||||
|
||||
if(alt['default'])
|
||||
self.title_select.set('value', alt.title);
|
||||
});
|
||||
|
||||
|
||||
Quality.getActiveProfiles().each(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 || {})['id']);
|
||||
|
||||
if(self.movie.profile)
|
||||
self.profile_select.set('value', self.movie.profile.data.id);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -437,6 +437,7 @@ body > .spinner, .mask{
|
||||
border-radius:3px;
|
||||
border: 1px solid #252930;
|
||||
box-shadow: inset 0 1px 0px rgba(255,255,255,0.20), 0 0 3px rgba(0,0,0, 0.2);
|
||||
background: rgb(55,62,74);
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
|
||||
@@ -362,28 +362,29 @@
|
||||
border-radius: 2px;
|
||||
}
|
||||
.page .tag_input > ul:hover > li.choice {
|
||||
background: url('../../images/sprite.png') no-repeat 94% -53px, -webkit-gradient(
|
||||
background: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0, rgba(255,255,255,0.1)),
|
||||
color-stop(1, rgba(255,255,255,0.3))
|
||||
);
|
||||
background: url('../../images/sprite.png') no-repeat 94% -53px, -moz-linear-gradient(
|
||||
background: -moz-linear-gradient(
|
||||
center top,
|
||||
rgba(255,255,255,0.3) 0%,
|
||||
rgba(255,255,255,0.1) 100%
|
||||
);
|
||||
}
|
||||
.page .tag_input > ul > li.choice:hover {
|
||||
background: url('../../images/sprite.png') no-repeat 94% -53px, -webkit-gradient(
|
||||
.page .tag_input > ul > li.choice:hover,
|
||||
.page .tag_input > ul > li.choice.selected {
|
||||
background: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0, #406db8),
|
||||
color-stop(1, #5b9bd1)
|
||||
);
|
||||
background: url('../../images/sprite.png') no-repeat 94% -53px, -moz-linear-gradient(
|
||||
background: -moz-linear-gradient(
|
||||
center top,
|
||||
#5b9bd1 0%,
|
||||
#406db8 100%
|
||||
@@ -436,7 +437,8 @@
|
||||
);
|
||||
background-size: 65%;
|
||||
}
|
||||
.page .tag_input .choice:hover .delete { display: inline-block; }
|
||||
.page .tag_input .choice:hover .delete,
|
||||
.page .tag_input .choice.selected .delete { display: inline-block; }
|
||||
.page .tag_input .choice .delete:hover {
|
||||
height: 14px;
|
||||
margin-top: -13px;
|
||||
@@ -587,4 +589,9 @@
|
||||
.group_userscript .bookmarklet span {
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.active .group_imdb_automation:not(.disabled) {
|
||||
background: url('../../images/imdb_watchlist.png') no-repeat right 50px;
|
||||
min-height: 210px;
|
||||
}
|
||||
24
init/freebsd
24
init/freebsd
@@ -31,24 +31,14 @@ load_rc_config ${name}
|
||||
: ${couchpotato_user:="_sabnzbd"}
|
||||
: ${couchpotato_dir:="/usr/local/couchpotato"}
|
||||
: ${couchpotato_chdir:="${couchpotato_dir}"}
|
||||
: ${couchpotato_pid:="${couchpotato_dir}/couchpotato.pid"}
|
||||
|
||||
WGET="/usr/local/bin/wget" # You need wget for this script to safely shutdown CouchPotato.
|
||||
HOST="127.0.0.1" # Set CouchPotato address here.
|
||||
PORT="8081" # Set CouchPotato port here.
|
||||
CPAPI="" # Set CouchPotato API key
|
||||
: ${couchpotato_pid:="/var/run/couchpotato.pid"}
|
||||
|
||||
pidfile="${couchpotato_pid}"
|
||||
status_cmd="${name}_status"
|
||||
stop_cmd="${name}_stop"
|
||||
|
||||
command="/usr/sbin/daemon"
|
||||
command_args="-f -p ${couchpotato_pid} python ${couchpotato_dir}/couchpotato.py ${couchpotato_flags}"
|
||||
|
||||
# Check for wget and refuse to start without it.
|
||||
if [ ! -x "${WGET}" ]; then
|
||||
warn "couchpotato not started: You need wget to safely shut down CouchPotato."
|
||||
exit 1
|
||||
fi
|
||||
command_args="-f -p ${couchpotato_pid} python ${couchpotato_dir}/CouchPotato.py ${couchpotato_flags} --pid_file=${couchpotato_pid}"
|
||||
|
||||
# Ensure user is root when running this script.
|
||||
if [ `id -u` != "0" ]; then
|
||||
@@ -59,19 +49,23 @@ fi
|
||||
verify_couchpotato_pid() {
|
||||
# Make sure the pid corresponds to the CouchPotato process.
|
||||
pid=`cat ${couchpotato_pid} 2>/dev/null`
|
||||
ps -p ${pid} | grep -q "python ${couchpotato_dir}/couchpotato.py"
|
||||
ps -p ${pid} | grep -q "python ${couchpotato_dir}/CouchPotato.py"
|
||||
return $?
|
||||
}
|
||||
|
||||
# Try to stop CouchPotato cleanly by calling shutdown over http.
|
||||
couchpotato_stop() {
|
||||
|
||||
echo "Stopping $name"
|
||||
verify_couchpotato_pid
|
||||
${WGET} -O - -q "http://${HOST}:${PORT}/${CPAPI}/app.shutdown/" >/dev/null
|
||||
|
||||
if [ -n "${pid}" ]; then
|
||||
kill -SIGTERM ${pid} 2> /dev/null
|
||||
wait_for_pids ${pid}
|
||||
kill -9 ${pid} 2> /dev/null
|
||||
echo "Stopped"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
couchpotato_status() {
|
||||
|
||||
Reference in New Issue
Block a user