diff --git a/CouchPotato.py b/CouchPotato.py
index c5fefe5..bf5b1f7 100755
--- a/CouchPotato.py
+++ b/CouchPotato.py
@@ -33,7 +33,7 @@ def start():
new_environ[key] = value.encode('iso-8859-1')
subprocess.call(args, env = new_environ)
- return os.path.isfile(os.path.join(options.data_dir, 'restart'))
+ return os.path.isfile(os.path.join(base_path, 'restart'))
except Exception, e:
log.critical(e)
return 0
diff --git a/couchpotato/api/__init__.py b/couchpotato/api/__init__.py
index 8063d0a..1f2f1a3 100644
--- a/couchpotato/api/__init__.py
+++ b/couchpotato/api/__init__.py
@@ -18,3 +18,4 @@ def index():
return jsonified({'routes': routes})
addApiView('', index)
+addApiView('default', index)
diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py
index fe6b067..2c54f43 100644
--- a/couchpotato/core/_base/_core/main.py
+++ b/couchpotato/core/_base/_core/main.py
@@ -1,5 +1,5 @@
from couchpotato.api import addApiView
-from couchpotato.core.event import fireEvent
+from couchpotato.core.event import fireEvent, addEvent
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
@@ -43,14 +43,13 @@ class Core(Plugin):
time.sleep(1)
-
if restart:
self.createFile(self.restartFilePath(), 'This is the most suckiest way to register if CP is restarted. Ever...')
- func = request.environ.get('werkzeug.server.shutdown')
- if func is None:
+ try:
+ request.environ.get('werkzeug.server.shutdown')()
+ except:
log.error('Failed shutting down the server')
- func()
def removeRestartFile(self):
try:
@@ -59,4 +58,4 @@ class Core(Plugin):
pass
def restartFilePath(self):
- return os.path.join(Env.get('data_dir'), 'restart')
+ return os.path.join(Env.get('app_dir'), 'restart')
diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py
index ceb0439..48ea3aa 100644
--- a/couchpotato/core/downloaders/base.py
+++ b/couchpotato/core/downloaders/base.py
@@ -1,6 +1,7 @@
from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
+from couchpotato.environment import Env
log = CPLog(__name__)
@@ -16,7 +17,10 @@ class Downloader(Plugin):
pass
def cpTag(self, movie):
- return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else ''
+ if Env.setting('enabled', 'renamer'):
+ return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else ''
+
+ return ''
def isDisabled(self):
return not self.isEnabled()
diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py
index f904461..f1be582 100644
--- a/couchpotato/core/event.py
+++ b/couchpotato/core/event.py
@@ -8,7 +8,7 @@ log = CPLog(__name__)
events = {}
-def addEvent(name, handler):
+def addEvent(name, handler, priority = 0):
if events.get(name):
e = events[name]
@@ -27,7 +27,10 @@ def addEvent(name, handler):
return h
- e += createHandle
+ if name == 'app.initialize':
+ print 'test', priority
+
+ e.handle(createHandle, priority = priority)
def removeEvent(name, handler):
e = events[name]
diff --git a/couchpotato/core/plugins/profile/main.py b/couchpotato/core/plugins/profile/main.py
index 86d8965..579b0cd 100644
--- a/couchpotato/core/plugins/profile/main.py
+++ b/couchpotato/core/plugins/profile/main.py
@@ -19,7 +19,7 @@ class ProfilePlugin(Plugin):
addApiView('profile.save_order', self.saveOrder)
addApiView('profile.delete', self.delete)
- addEvent('app.initialize', self.fill)
+ addEvent('app.initialize', self.fill, priority = 90)
def all(self):
@@ -127,7 +127,7 @@ class ProfilePlugin(Plugin):
}]
# Create default quality profile
- order = 99
+ order = -2
for profile in profiles:
log.info('Creating default profile: %s' % profile.get('label'))
p = Profile(
diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py
index d01dc36..c2089d4 100644
--- a/couchpotato/core/plugins/quality/main.py
+++ b/couchpotato/core/plugins/quality/main.py
@@ -32,7 +32,7 @@ class QualityPlugin(Plugin):
addEvent('quality.single', self.single)
addEvent('quality.guess', self.guess)
- addEvent('app.initialize', self.fill)
+ addEvent('app.initialize', self.fill, priority = 10)
def all(self):
diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py
index bd13b0d..8cc8e50 100644
--- a/couchpotato/core/plugins/searcher/main.py
+++ b/couchpotato/core/plugins/searcher/main.py
@@ -86,7 +86,7 @@ class Searcher(Plugin):
for info in nzb:
try:
- if not isinstance(nzb[info], (str, unicode)):
+ if not isinstance(nzb[info], (str, unicode, int, long)):
continue
rls_info = ReleaseInfo(
diff --git a/couchpotato/core/plugins/updater/main.py b/couchpotato/core/plugins/updater/main.py
index 5b2dea6..edd5e3f 100644
--- a/couchpotato/core/plugins/updater/main.py
+++ b/couchpotato/core/plugins/updater/main.py
@@ -1,4 +1,6 @@
+from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent
+from couchpotato.core.helpers.request import jsonified
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
@@ -10,14 +12,13 @@ log = CPLog(__name__)
class Updater(Plugin):
- git = 'git://github.com/CouchPotato/CouchPotato.git'
+ repo_name = 'RuudBurger/CouchPotatoServer'
running = False
version = None
- updateFailed = False
- updateAvailable = False
- updateVersion = None
- lastCheck = 0
+ update_failed = False
+ update_version = None
+ last_check = 0
def __init__(self):
@@ -27,6 +28,18 @@ class Updater(Plugin):
addEvent('app.load', self.check)
+ addApiView('updater.info', self.getInfo)
+ addApiView('updater.update', self.doUpdateView)
+
+ def getInfo(self):
+
+ return jsonified({
+ 'repo_name': self.repo_name,
+ 'last_check': self.last_check,
+ 'update_version': self.update_version,
+ 'version': self.getVersion(),
+ })
+
def getVersion(self):
if not self.version:
@@ -42,7 +55,7 @@ class Updater(Plugin):
def check(self):
- if self.updateAvailable or self.isDisabled():
+ if self.update_version or self.isDisabled():
return
current_branch = self.repo.getCurrentBranch().name
@@ -54,13 +67,17 @@ class Updater(Plugin):
remote = branch.getHead()
if local.getDate() < remote.getDate():
- if self.conf('automatic') and not self.updateFailed:
+ if self.conf('automatic') and not self.update_failed:
self.doUpdate()
else:
- self.updateAvailable = True
- self.updateVersion = remote.hash
+ self.update_version = remote.hash
+
+ self.last_check = time.time()
- self.lastCheck = time.time()
+ def doUpdateView(self):
+ return jsonified({
+ 'success': self.doUpdate()
+ })
def doUpdate(self):
try:
@@ -70,7 +87,7 @@ class Updater(Plugin):
except Exception, e:
log.error('Failed updating via GIT: %s' % e)
- self.updateFailed = True
+ self.update_failed = True
return False
diff --git a/couchpotato/core/plugins/updater/static/updater.js b/couchpotato/core/plugins/updater/static/updater.js
new file mode 100644
index 0000000..f7268b2
--- /dev/null
+++ b/couchpotato/core/plugins/updater/static/updater.js
@@ -0,0 +1,89 @@
+var UpdaterBase = new Class({
+
+ initialize: function(){
+ var self = this;
+
+ App.addEvent('load', self.info.bind(self, 1000))
+ },
+
+ info: function(timeout){
+ var self = this;
+
+ if(self.timer) clearTimeout(self.timer);
+
+ self.timer = setTimeout(function(){
+ Api.request('updater.info', {
+ 'onComplete': function(json){
+ if(json.update_version){
+ self.createMessage(json);
+ }
+ else {
+ if(self.message)
+ self.message.destroy();
+ }
+ }
+ })
+ }, (timeout || 0))
+
+ },
+
+ createMessage: function(data){
+ var self = this;
+
+ self.message = new Element('div.message.update').adopt(
+ new Element('span', {
+ 'text': 'A new version is available'
+ }),
+ new Element('a', {
+ 'href': 'https://github.com/'+data.repo_name+'/compare/'+data.version.substr(0, 7)+'...'+data.update_version.substr(0, 7),
+ 'text': 'see what has changed',
+ 'target': '_blank'
+ }),
+ new Element('span[text=or]'),
+ new Element('a', {
+ 'text': 'just update, gogogo!',
+ 'events': {
+ 'click': self.doUpdate.bind(self)
+ }
+ })
+ ).inject($(document.body).getElement('.header'))
+ },
+
+ doUpdate: function(){
+ var self = this;
+
+ Api.request('updater.update', {
+ 'onComplete': function(json){
+
+ if(json.success){
+ App.restart();
+
+ $(document.body).set('spin', {
+ 'message': 'Updating'
+ });
+ $(document.body).spin();
+
+ var checks = 0;
+ var interval = 0;
+ interval = setInterval(function(){
+ Api.request('', {
+ 'onSuccess': function(){
+ if(checks > 2){
+ clearInterval(interval);
+ $(document.body).unspin();
+ self.info();
+ }
+ }
+ });
+ checks++;
+ }, 500)
+
+ }
+
+ }
+ });
+ }
+
+});
+
+var Updater = new UpdaterBase();
diff --git a/couchpotato/core/providers/nzb/newznab/__init__.py b/couchpotato/core/providers/nzb/newznab/__init__.py
index fa2f351..341e6fd 100644
--- a/couchpotato/core/providers/nzb/newznab/__init__.py
+++ b/couchpotato/core/providers/nzb/newznab/__init__.py
@@ -21,7 +21,7 @@ config = [{
},
{
'name': 'host',
- 'default': 'http://nzb.su',
+ 'default': 'nzb.su',
'description': 'The hostname of your newznab provider'
},
{
diff --git a/couchpotato/core/providers/nzb/newznab/static/newznab.js b/couchpotato/core/providers/nzb/newznab/static/newznab.js
index 793a4ea..e82ec35 100644
--- a/couchpotato/core/providers/nzb/newznab/static/newznab.js
+++ b/couchpotato/core/providers/nzb/newznab/static/newznab.js
@@ -72,7 +72,7 @@ var MultipleNewznab = new Class({
if(has_empty) return;
self.add_empty_timeout = setTimeout(function(){
- self.createItem(false);
+ self.createItem(false, null, null);
}, 10);
},
diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js
index 6512829..95758b1 100644
--- a/couchpotato/static/scripts/couchpotato.js
+++ b/couchpotato/static/scripts/couchpotato.js
@@ -29,7 +29,7 @@ var CouchPotato = new Class({
else
self.openPage(window.location.pathname);
- self.c.addEvent('click:relay(a)', self.pushState.bind(self));
+ self.c.addEvent('click:relay(a:not([target=_blank]))', self.pushState.bind(self));
},
pushState: function(e){
@@ -100,6 +100,14 @@ var CouchPotato = new Class({
getPage: function(name){
return this.pages[name]
+ },
+
+ shutdown: function(){
+ Api.request('app.shutdown');
+ },
+
+ restart: function(){
+ Api.request('app.restart');
}
});
diff --git a/couchpotato/static/scripts/library/form_replacement/form_check.js b/couchpotato/static/scripts/library/form_replacement/form_check.js
index c246400..4c240f6 100644
--- a/couchpotato/static/scripts/library/form_replacement/form_check.js
+++ b/couchpotato/static/scripts/library/form_replacement/form_check.js
@@ -96,14 +96,14 @@ Form.Check = new Class({
this.fireEvent('removeHighlight', this);
},
keyToggle: function(e) {
- var evt = new Event(e);
+ var evt = (e);
if (evt.key === 'space') { this.toggle(e); }
},
toggle: function(e) {
var evt;
if (this.disabled) { return this; }
if (e) {
- evt = new Event(e).stopPropagation();
+ evt = (e).stopPropagation();
if (evt.target.tagName.toLowerCase() !== 'a') {
evt.stop();
}
diff --git a/couchpotato/static/scripts/library/form_replacement/form_dropdown.js b/couchpotato/static/scripts/library/form_replacement/form_dropdown.js
index 0b01adf..86c2c3c 100644
--- a/couchpotato/static/scripts/library/form_replacement/form_dropdown.js
+++ b/couchpotato/static/scripts/library/form_replacement/form_dropdown.js
@@ -117,7 +117,7 @@ Form.Dropdown = new Class({
},
expand: function(e) {
clearTimeout(this.collapseInterval);
- var evt = e ? new Event(e).stop() : null;
+ var evt = e ? (e).stop() : null;
this.open = true;
this.input.focus();
this.element.addClass('active').addClass('dropdown-active');
diff --git a/couchpotato/static/scripts/library/form_replacement/form_radio.js b/couchpotato/static/scripts/library/form_replacement/form_radio.js
index 2fa15f7..245aa4d 100644
--- a/couchpotato/static/scripts/library/form_replacement/form_radio.js
+++ b/couchpotato/static/scripts/library/form_replacement/form_radio.js
@@ -22,7 +22,7 @@ Form.Radio = new Class({
toggle: function(e) {
if (this.element.hasClass('checked') || this.disabled) { return; }
var evt;
- if (e) { evt = new Event(e).stop(); }
+ if (e) { evt = (e).stop(); }
if (this.checked) {
this.uncheck();
} else {
diff --git a/couchpotato/static/scripts/library/mootools.js b/couchpotato/static/scripts/library/mootools.js
index 1d54e88..9f25f67 100644
--- a/couchpotato/static/scripts/library/mootools.js
+++ b/couchpotato/static/scripts/library/mootools.js
@@ -3,10 +3,10 @@
MooTools: the javascript framework
web build:
- - http://mootools.net/core/3e4fc9be110c01367e16b46df47d66b7
+ - http://mootools.net/core/f42fb6d73ea1a13146c5ad9502b442f0
packager build:
- - packager build Core/Class Core/Class.Extras Core/Element Core/Element.Style Core/Element.Dimensions Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request.JSON Core/Cookie Core/DOMReady
+ - packager build Core/Class Core/Class.Extras Core/Element Core/Element.Style Core/Element.Delegation Core/Element.Dimensions Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request.JSON Core/Cookie Core/DOMReady
/*
---
@@ -3611,6 +3611,514 @@ Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, bor
/*
---
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function() {
+
+var _keys = {};
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ this.key = _keys[code];
+ if (type == 'keydown'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'mousewheel')
+ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout'){
+ var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.from(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.from(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.from(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, oninput: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ error: 1, abort: 1, scroll: 1 //misc
+};
+
+var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+Element.Events = {
+
+ mouseenter: {
+ base: 'mouseover',
+ condition: check
+ },
+
+ mouseleave: {
+ base: 'mouseout',
+ condition: check
+ },
+
+ mousewheel: {
+ base: (Browser.firefox) ? 'DOMMouseScroll' : 'mousewheel'
+ }
+
+};
+
+/**/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change'
+ },
+ condition: function(event){
+ return !!(this.type != 'radio' || this.checked);
+ }
+ }
+}
+/**/
+
+
+
+})();
+
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event){
+ var target = event.target;
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover'
+ },
+ mouseleave: {
+ base: 'mouseout'
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/**/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, uid){
+ var target = event.target,
+ form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/**/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (elementEvent && elementEvent.condition){
+ var __match = match, condition = elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event){
+ _map.listen(self, match, fn, event, uid);
+ } : function(event){
+ bubbleUp(self, match, fn, event);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+
+/*
+---
+
name: Element.Dimensions
description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
@@ -4867,440 +5375,132 @@ JSON.decode = function(string, secure){
return eval('(' + string + ')');
};
-})();
-
-
-/*
----
-
-name: Request.JSON
-
-description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
-
-license: MIT-style license.
-
-requires: [Request, JSON]
-
-provides: Request.JSON
-
-...
-*/
-
-Request.JSON = new Class({
-
- Extends: Request,
-
- options: {
- /*onError: function(text, error){},*/
- secure: true
- },
-
- initialize: function(options){
- this.parent(options);
- Object.append(this.headers, {
- 'Accept': 'application/json',
- 'X-Request': 'JSON'
- });
- },
-
- success: function(text){
- var json;
- try {
- json = this.response.json = JSON.decode(text, this.options.secure);
- } catch (error){
- this.fireEvent('error', [text, error]);
- return;
- }
- if (json == null) this.onFailure();
- else this.onSuccess(json, text);
- }
-
-});
-
-
-/*
----
-
-name: Cookie
-
-description: Class for creating, reading, and deleting browser Cookies.
-
-license: MIT-style license.
-
-credits:
- - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
-
-requires: [Options, Browser]
-
-provides: Cookie
-
-...
-*/
-
-var Cookie = new Class({
-
- Implements: Options,
-
- options: {
- path: '/',
- domain: false,
- duration: false,
- secure: false,
- document: document,
- encode: true
- },
-
- initialize: function(key, options){
- this.key = key;
- this.setOptions(options);
- },
-
- write: function(value){
- if (this.options.encode) value = encodeURIComponent(value);
- if (this.options.domain) value += '; domain=' + this.options.domain;
- if (this.options.path) value += '; path=' + this.options.path;
- if (this.options.duration){
- var date = new Date();
- date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
- value += '; expires=' + date.toGMTString();
- }
- if (this.options.secure) value += '; secure';
- this.options.document.cookie = this.key + '=' + value;
- return this;
- },
-
- read: function(){
- var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
- return (value) ? decodeURIComponent(value[1]) : null;
- },
-
- dispose: function(){
- new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
- return this;
- }
-
-});
-
-Cookie.write = function(key, value, options){
- return new Cookie(key, options).write(value);
-};
-
-Cookie.read = function(key){
- return new Cookie(key).read();
-};
-
-Cookie.dispose = function(key, options){
- return new Cookie(key, options).dispose();
-};
-
-
-/*
----
-
-name: Event
-
-description: Contains the Event Type, to make the event object cross-browser.
-
-license: MIT-style license.
-
-requires: [Window, Document, Array, Function, String, Object]
-
-provides: Event
-
-...
-*/
-
-(function() {
-
-var _keys = {};
-
-var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
- if (!win) win = window;
- event = event || win.event;
- if (event.$extended) return event;
- this.event = event;
- this.$extended = true;
- this.shift = event.shiftKey;
- this.control = event.ctrlKey;
- this.alt = event.altKey;
- this.meta = event.metaKey;
- var type = this.type = event.type;
- var target = event.target || event.srcElement;
- while (target && target.nodeType == 3) target = target.parentNode;
- this.target = document.id(target);
-
- if (type.indexOf('key') == 0){
- var code = this.code = (event.which || event.keyCode);
- this.key = _keys[code];
- if (type == 'keydown'){
- if (code > 111 && code < 124) this.key = 'f' + (code - 111);
- else if (code > 95 && code < 106) this.key = code - 96;
- }
- if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
- } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type.indexOf('mouse') == 0){
- var doc = win.document;
- doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
- this.page = {
- x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
- y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
- };
- this.client = {
- x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
- y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
- };
- if (type == 'DOMMouseScroll' || type == 'mousewheel')
- this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
-
- this.rightClick = (event.which == 3 || event.button == 2);
- if (type == 'mouseover' || type == 'mouseout'){
- var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
- while (related && related.nodeType == 3) related = related.parentNode;
- this.relatedTarget = document.id(related);
- }
- } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
- this.rotation = event.rotation;
- this.scale = event.scale;
- this.targetTouches = event.targetTouches;
- this.changedTouches = event.changedTouches;
- var touches = this.touches = event.touches;
- if (touches && touches[0]){
- var touch = touches[0];
- this.page = {x: touch.pageX, y: touch.pageY};
- this.client = {x: touch.clientX, y: touch.clientY};
- }
- }
+})();
- if (!this.client) this.client = {};
- if (!this.page) this.page = {};
-});
-DOMEvent.implement({
+/*
+---
- stop: function(){
- return this.preventDefault().stopPropagation();
- },
+name: Request.JSON
- stopPropagation: function(){
- if (this.event.stopPropagation) this.event.stopPropagation();
- else this.event.cancelBubble = true;
- return this;
- },
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
- preventDefault: function(){
- if (this.event.preventDefault) this.event.preventDefault();
- else this.event.returnValue = false;
- return this;
- }
+license: MIT-style license.
-});
+requires: [Request, JSON]
-DOMEvent.defineKey = function(code, key){
- _keys[code] = key;
- return this;
-};
+provides: Request.JSON
-DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+...
+*/
-DOMEvent.defineKeys({
- '38': 'up', '40': 'down', '37': 'left', '39': 'right',
- '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
- '46': 'delete', '13': 'enter'
-});
+Request.JSON = new Class({
-})();
+ Extends: Request,
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null) this.onFailure();
+ else this.onSuccess(json, text);
+ }
+});
/*
---
-name: Element.Event
+name: Cookie
-description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.
+description: Class for creating, reading, and deleting browser Cookies.
license: MIT-style license.
-requires: [Element, Event]
+credits:
+ - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
-provides: Element.Event
+requires: [Options, Browser]
+
+provides: Cookie
...
*/
-(function(){
-
-Element.Properties.events = {set: function(events){
- this.addEvents(events);
-}};
-
-[Element, Window, Document].invoke('implement', {
+var Cookie = new Class({
- addEvent: function(type, fn){
- var events = this.retrieve('events', {});
- if (!events[type]) events[type] = {keys: [], values: []};
- if (events[type].keys.contains(fn)) return this;
- events[type].keys.push(fn);
- var realType = type,
- custom = Element.Events[type],
- condition = fn,
- self = this;
- if (custom){
- if (custom.onAdd) custom.onAdd.call(this, fn, type);
- if (custom.condition){
- condition = function(event){
- if (custom.condition.call(this, event, type)) return fn.call(this, event);
- return true;
- };
- }
- if (custom.base) realType = Function.from(custom.base).call(this, type);
- }
- var defn = function(){
- return fn.call(self);
- };
- var nativeEvent = Element.NativeEvents[realType];
- if (nativeEvent){
- if (nativeEvent == 2){
- defn = function(event){
- event = new DOMEvent(event, self.getWindow());
- if (condition.call(self, event) === false) event.stop();
- };
- }
- this.addListener(realType, defn, arguments[2]);
- }
- events[type].values.push(defn);
- return this;
- },
+ Implements: Options,
- removeEvent: function(type, fn){
- var events = this.retrieve('events');
- if (!events || !events[type]) return this;
- var list = events[type];
- var index = list.keys.indexOf(fn);
- if (index == -1) return this;
- var value = list.values[index];
- delete list.keys[index];
- delete list.values[index];
- var custom = Element.Events[type];
- if (custom){
- if (custom.onRemove) custom.onRemove.call(this, fn, type);
- if (custom.base) type = Function.from(custom.base).call(this, type);
- }
- return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ options: {
+ path: '/',
+ domain: false,
+ duration: false,
+ secure: false,
+ document: document,
+ encode: true
},
- addEvents: function(events){
- for (var event in events) this.addEvent(event, events[event]);
- return this;
+ initialize: function(key, options){
+ this.key = key;
+ this.setOptions(options);
},
- removeEvents: function(events){
- var type;
- if (typeOf(events) == 'object'){
- for (type in events) this.removeEvent(type, events[type]);
- return this;
- }
- var attached = this.retrieve('events');
- if (!attached) return this;
- if (!events){
- for (type in attached) this.removeEvents(type);
- this.eliminate('events');
- } else if (attached[events]){
- attached[events].keys.each(function(fn){
- this.removeEvent(events, fn);
- }, this);
- delete attached[events];
+ write: function(value){
+ if (this.options.encode) value = encodeURIComponent(value);
+ if (this.options.domain) value += '; domain=' + this.options.domain;
+ if (this.options.path) value += '; path=' + this.options.path;
+ if (this.options.duration){
+ var date = new Date();
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+ value += '; expires=' + date.toGMTString();
}
+ if (this.options.secure) value += '; secure';
+ this.options.document.cookie = this.key + '=' + value;
return this;
},
- fireEvent: function(type, args, delay){
- var events = this.retrieve('events');
- if (!events || !events[type]) return this;
- args = Array.from(args);
-
- events[type].keys.each(function(fn){
- if (delay) fn.delay(delay, this, args);
- else fn.apply(this, args);
- }, this);
- return this;
+ read: function(){
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+ return (value) ? decodeURIComponent(value[1]) : null;
},
- cloneEvents: function(from, type){
- from = document.id(from);
- var events = from.retrieve('events');
- if (!events) return this;
- if (!type){
- for (var eventType in events) this.cloneEvents(from, eventType);
- } else if (events[type]){
- events[type].keys.each(function(fn){
- this.addEvent(type, fn);
- }, this);
- }
+ dispose: function(){
+ new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
return this;
}
});
-Element.NativeEvents = {
- click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
- mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
- mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
- keydown: 2, keypress: 2, keyup: 2, //keyboard
- orientationchange: 2, // mobile
- touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
- gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
- focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, oninput: 2, //form elements
- load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
- error: 1, abort: 1, scroll: 1 //misc
+Cookie.write = function(key, value, options){
+ return new Cookie(key, options).write(value);
};
-var check = function(event){
- var related = event.relatedTarget;
- if (related == null) return true;
- if (!related) return false;
- return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+Cookie.read = function(key){
+ return new Cookie(key).read();
};
-Element.Events = {
-
- mouseenter: {
- base: 'mouseover',
- condition: check
- },
-
- mouseleave: {
- base: 'mouseout',
- condition: check
- },
-
- mousewheel: {
- base: (Browser.firefox) ? 'DOMMouseScroll' : 'mousewheel'
- }
-
+Cookie.dispose = function(key, options){
+ return new Cookie(key, options).dispose();
};
-/**/
-if (!window.addEventListener){
- Element.NativeEvents.propertychange = 2;
- Element.Events.change = {
- base: function(){
- var type = this.type;
- return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change'
- },
- condition: function(event){
- return !!(this.type != 'radio' || this.checked);
- }
- }
-}
-/**/
-
-
-
-})();
-
/*
---
diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js
index 9fb710b..2c5ca29 100644
--- a/couchpotato/static/scripts/page/settings.js
+++ b/couchpotato/static/scripts/page/settings.js
@@ -184,7 +184,7 @@ Page.Settings = new Class({
'text': (group.label || group.name).capitalize()
}).adopt(
new Element('span.hint', {
- 'html': group.description
+ 'html': group.description || ''
})
)
)
diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js
index b1a60c6..c2814d6 100644
--- a/couchpotato/static/scripts/page/wanted.js
+++ b/couchpotato/static/scripts/page/wanted.js
@@ -8,11 +8,11 @@ Page.Wanted = new Class({
indexAction: function(param){
var self = this;
- if(!self.list){
+ if(!self.wanted){
// Wanted movies
self.wanted = new MovieList({
- 'status': ['active', 'snatched'],
+ 'status': 'active',
'actions': MovieActions
});
$(self.wanted).inject(self.el);
diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css
index 293c568..232a8c7 100644
--- a/couchpotato/static/style/main.css
+++ b/couchpotato/static/style/main.css
@@ -1,6 +1,7 @@
/* @override
http://localhost:5000/static/style/main.css
http://192.168.1.20:5000/static/style/main.css
+ http://127.0.0.1:5000/static/style/main.css
*/
html {
@@ -211,6 +212,27 @@ form {
.header .navigation li a:hover, .header .navigation li a:active {
color: #b1d8dc;
}
+
+ .header .message {
+ text-align: center;
+ position: relative;
+ top: -70px;
+ padding: 15px 0 20px;
+ background: #ff6134;
+ font-size: 26px;
+
+ border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -webkit-border-radius: 0 0 5px 5px;
+
+ box-shadow: 0 2px 1px rgba(0,0,0, 0.3);
+ -moz-box-shadow: 0 2px 1px rgba(0,0,0, 0.3);
+ -webkit-box-shadow: 0 2px 1px rgba(0,0,0, 0.3);
+ }
+
+ .header .message a {
+ padding: 0 10px;
+ }
/*** Global Styles ***/
.check {
diff --git a/libs/axl/axel.py b/libs/axl/axel.py
index f97e745..2b0d265 100644
--- a/libs/axl/axel.py
+++ b/libs/axl/axel.py
@@ -11,7 +11,10 @@
# Source: http://pypi.python.org/pypi/axel
# Docs: http://packages.python.org/axel
-import sys, threading, Queue
+from couchpotato.core.helpers.variable import natcmp
+import Queue
+import sys
+import threading
class Event(object):
"""
@@ -100,7 +103,7 @@ class Event(object):
self.handlers = {}
self.memoize = {}
- def handle(self, handler):
+ def handle(self, handler, priority = 0):
""" Registers a handler. The handler can be transmitted together
with two arguments as a list or dictionary. The arguments are:
@@ -118,7 +121,7 @@ class Event(object):
event += {'handler':handler, 'memoize':True, 'timeout':1.5}
"""
handler_, memoize, timeout = self._extract(handler)
- self.handlers[hash(handler_)] = (handler_, memoize, timeout)
+ self.handlers['%s.%s' % (priority, hash(handler_))] = (handler_, memoize, timeout)
return self
def unhandle(self, handler):
@@ -144,7 +147,7 @@ class Event(object):
t.daemon = True
t.start()
- for handler in self.handlers:
+ for handler in sorted(self.handlers.iterkeys(), cmp = natcmp):
self.queue.put(handler)
if self.asynchronous: