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: