diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py index 0390afc0..7058b111 100644 --- a/couchpotato/__init__.py +++ b/couchpotato/__init__.py @@ -15,7 +15,7 @@ import os log = CPLog(__name__) -app = Flask(__name__) +app = Flask(__name__, static_folder = 'nope') web = Blueprint('web', __name__) diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index 007d1723..1e318fbb 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -70,7 +70,7 @@ class Core(Plugin): def crappyShutdown(self): try: - self.urlopen('%sapp.shutdown' % self.createApiUrl(), show_error = False) + self.urlopen('%s/app.shutdown' % self.createApiUrl(), show_error = False) return True except: self.initShutdown() @@ -78,7 +78,7 @@ class Core(Plugin): def crappyRestart(self): try: - self.urlopen('%sapp.restart' % self.createApiUrl(), show_error = False) + self.urlopen('%s/app.restart' % self.createApiUrl(), show_error = False) return True except: self.initShutdown(restart = True) @@ -163,8 +163,7 @@ class Core(Plugin): host = 'localhost' port = Env.setting('port') - return '%s:%d' % (cleanHost(host).rstrip('/'), int(port)) + return '%s:%d%s' % (cleanHost(host).rstrip('/'), int(port), '/' + Env.setting('url_base').lstrip('/') if Env.setting('url_base') else '') def createApiUrl(self): - - return '%s/%s/' % (self.createBaseUrl(), Env.setting('api_key')) + return '%s/%s' % (self.createBaseUrl(), Env.setting('api_key')) diff --git a/couchpotato/core/auth.py b/couchpotato/core/auth.py index 743a4c79..032bdf28 100644 --- a/couchpotato/core/auth.py +++ b/couchpotato/core/auth.py @@ -17,7 +17,7 @@ def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = getattr(request, 'authorization') - if Env.setting('username') and (not auth or not check_auth(auth.username, md5(auth.password))): + if Env.setting('username') and Env.setting('password') and (not auth or not check_auth(auth.username, md5(auth.password))): return authenticate() return f(*args, **kwargs) diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py index 546883d8..232c7403 100644 --- a/couchpotato/core/event.py +++ b/couchpotato/core/event.py @@ -2,6 +2,7 @@ from axl.axel import Event from couchpotato.core.helpers.variable import mergeDicts from couchpotato.core.logger import CPLog import threading +import time import traceback log = CPLog(__name__) @@ -25,15 +26,17 @@ def addEvent(name, handler, priority = 100): try: parent = handler.im_self - parent.beforeCall(handler) + bc = hasattr(parent, 'beforeCall') + if bc: parent.beforeCall(handler) h = runHandler(name, handler, *args, **kwargs) - parent.afterCall(handler) + ac = hasattr(parent, 'afterCall') + if ac: parent.afterCall(handler) except: h = runHandler(name, handler, *args, **kwargs) return h - e.handle(createHandle, priority = priority) + e.handle(handler, priority = priority) def removeEvent(name, handler): e = events[name] @@ -64,8 +67,10 @@ def fireEvent(name, *args, **kwargs): except: pass e = events[name] + e.lock.acquire() e.asynchronous = False result = e(*args, **kwargs) + e.lock.release() if single and not merge: results = None @@ -123,10 +128,11 @@ def fireEventAsync(name, *args, **kwargs): #log.debug('Async "%s": %s, %s' % (name, args, kwargs)) try: e = events[name] + e.lock.acquire() e.asynchronous = True e.error_handler = errorHandler - e(*args, **kwargs) + e.lock.release() return True except Exception, e: log.error('%s: %s' % (name, e)) diff --git a/couchpotato/core/notifications/growl/main.py b/couchpotato/core/notifications/growl/main.py index fb80bbd7..a37e8ef3 100644 --- a/couchpotato/core/notifications/growl/main.py +++ b/couchpotato/core/notifications/growl/main.py @@ -1,7 +1,11 @@ +from couchpotato.core.event import fireEvent from couchpotato.core.logger import CPLog from couchpotato.core.notifications.base import Notification from gntp import notifier import logging +import thread +import time +import traceback log = CPLog(__name__) @@ -15,13 +19,19 @@ class Growl(Notification): logger.disabled = True try: - self.growl = notifier.GrowlNotifier( - applicationName = 'CouchPotato', - notifications = ["Updates"], - defaultNotifications = ["Updates"], - applicationIcon = 'http://couchpota.to/media/images/couch.png', - ) - self.growl.register() + def startGrowl(): + time.sleep(2) + try: + self.growl = notifier.GrowlNotifier( + applicationName = 'CouchPotato', + notifications = ["Updates"], + defaultNotifications = ["Updates"], + applicationIcon = '%s/static/images/couch.png' % fireEvent('app.api_url', single = True), + ) + self.growl.register() + except: + log.error('Failed register of growl: %s' % traceback.format_exc()) + thread.start_new_thread(startGrowl, ()) except: pass diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index 6648fdd0..6eb1d020 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -54,7 +54,7 @@ class Plugin(object): s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', self.__class__.__name__) class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() - path = 'static/' + class_name + '/' + path = '%s/static/%s/' % (Env.setting('api_key'), class_name) addView(path + '', self.showStatic, static = True) if add_to_head: @@ -94,8 +94,6 @@ class Plugin(object): # http request def urlopen(self, url, timeout = 10, params = {}, headers = {}, multipart = False, show_error = True): - socket.setdefaulttimeout(timeout) - # Fill in some headers if not headers.get('Referer'): headers['Referer'] = urlparse(url).hostname @@ -114,13 +112,13 @@ class Plugin(object): cookies = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler) - data = opener.open(request).read() + data = opener.open(request, timeout = timeout).read() else: log.info('Opening url: %s, params: %s' % (url, [x for x in params.iterkeys()])) data = urllib.urlencode(params) if len(params) > 0 else None request = urllib2.Request(url, data, headers) - data = urllib2.urlopen(request).read() + data = urllib2.urlopen(request, timeout = timeout).read() except IOError: if show_error: log.error('Failed opening url in %s: %s %s' % (self.getName(), url, traceback.format_exc(1))) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 98c7b102..2700dc64 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -290,11 +290,11 @@ class Renamer(Plugin): # Remove files for src in remove_files: - log.info('Removing "%s"' % src) + log.info('(fake) Removing "%s"' % src) # Remove matching releases for release in remove_releases: - log.info('Removing release %s' % release) + log.info('(fake) Removing release %s' % release) # Search for trailers etc fireEventAsync('renamer.after', group) diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index 2ddb94cf..40d24b76 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -528,7 +528,9 @@ class Scanner(Plugin): return True def isSampleFile(self, filename): - return re.search('(^|[\W_])sample\d*[\W_]', filename.lower()) + is_sample = re.search('(^|[\W_])sample\d*[\W_]', filename.lower()) + if is_sample: log.debug('Is sample file: %s' % filename) + return is_sample def filesizeBetween(self, file, min = 0, max = 100000): try: diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index adf3c91d..fedd01dd 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -16,18 +16,18 @@ log = CPLog(__name__) class Searcher(Plugin): def __init__(self): - addEvent('searcher.all', self.all) + addEvent('searcher.all', self.all_movies) addEvent('searcher.single', self.single) addEvent('searcher.correct_movie', self.correctMovie) addEvent('searcher.download', self.download) # Schedule cronjob - fireEvent('schedule.cron', 'searcher.all', self.all, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) + fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) if not Env.setting('development'): - addEvent('app.load', self.all) + addEvent('app.load', self.all_movies) - def all(self): + def all_movies(self): db = get_session() diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py index 5b36e6ad..5ef04523 100644 --- a/couchpotato/core/settings/__init__.py +++ b/couchpotato/core/settings/__init__.py @@ -9,7 +9,7 @@ import os.path import time -class Settings(): +class Settings(object): options = {} types = {} diff --git a/couchpotato/runner.py b/couchpotato/runner.py index a331d93b..1888906c 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -10,8 +10,8 @@ import locale import logging import os.path import sys -import traceback import time +import traceback def getOptions(base_path, args): @@ -180,7 +180,8 @@ def runCouchPotato(options, base_path, args, desktop = None): } # Static path - web.add_url_rule('static/', + app.static_folder = os.path.join(base_path, 'couchpotato', 'static') + web.add_url_rule('%s/static/' % api_key, endpoint = 'static', view_func = app.send_static_file) diff --git a/couchpotato/static/images/couch.png b/couchpotato/static/images/couch.png new file mode 100644 index 00000000..0910c5cb Binary files /dev/null and b/couchpotato/static/images/couch.png differ diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index d656e422..c2ac6c08 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -13,6 +13,7 @@ Page.Wanted = new Class({ // Wanted movies self.wanted = new MovieList({ 'status': 'active', + 'navigation': true, 'actions': MovieActions }); $(self.wanted).inject(self.el); diff --git a/libs/axl/axel.py b/libs/axl/axel.py index de6da8a3..bbd145bd 100644 --- a/libs/axl/axel.py +++ b/libs/axl/axel.py @@ -6,59 +6,61 @@ # http://www.valuedlessons.com/2008/04/events-in-python.html # # This module is part of Axel and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php +# the MIT License: http://www.opensource.org/licenses/mit-license.php # # Source: http://pypi.python.org/pypi/axel # Docs: http://packages.python.org/axel from couchpotato.core.helpers.variable import natcmp import Queue +import hashlib import sys import threading +import time class Event(object): - """ + """ Event object inspired by C# events. Handlers can be registered and - unregistered using += and -= operators. Execution and result are + unregistered using += and -= operators. Execution and result are influenced by the arguments passed to the constructor and += method. - + from axel import Event - + event = Event() def on_event(*args, **kwargs): return (args, kwargs) - + event += on_event # handler registration - print(event(10, 20, y=30)) + print(event(10, 20, y=30)) >> ((True, ((10, 20), {'y': 30}), ),) - + event -= on_event # handler is unregistered - print(event(10, 20, y=30)) + print(event(10, 20, y=30)) >> None - + class Mouse(object): def __init__(self): self.click = Event(self) self.click += self.on_click # handler registration - + def on_click(self, sender, *args, **kwargs): assert isinstance(sender, Mouse), 'Wrong sender' return (args, kwargs) - + mouse = Mouse() - print(mouse.click(10, 20)) - >> ((True, ((10, 20), {}), + print(mouse.click(10, 20)) + >> ((True, ((10, 20), {}), >> >),) - + mouse.click -= mouse.on_click # handler is unregistered - print(mouse.click(10, 20)) + print(mouse.click(10, 20)) >> None """ def __init__(self, sender = None, asynch = False, exc_info = False, lock = None, threads = 3, traceback = False): - """ Creates an event - + """ Creates an event + asynch if True handler's are executes asynchronous exc_info @@ -66,31 +68,31 @@ class Event(object): lock threading.RLock used to synchronize execution sender - event's sender. The sender is passed as the first argument to the + event's sender. The sender is passed as the first argument to the handler, only if is not None. For this case the handler must have a placeholder in the arguments to receive the sender threads maximum number of threads that will be started traceback - if True, the execution result will contain sys.exc_info() - on error. exc_info must be also True to get the traceback - - hash = hash(handler) - - Handlers are stored in a dictionary that has as keys the handler's hash - handlers = { + if True, the execution result will contain sys.exc_info() + on error. exc_info must be also True to get the traceback + + hash = self.hash(handler) + + Handlers are stored in a dictionary that has as keys the handler's hash + handlers = { hash : (handler, memoize, timeout), - hash : (handler, memoize, timeout), ... + hash : (handler, memoize, timeout), ... + } + The execution result is cached using the following structure + memoize = { + hash : ((args, kwargs, result), (args, kwargs, result), ...), + hash : ((args, kwargs, result), ...), ... } - The execution result is cached using the following structure - memoize = { - hash : ((args, kwargs, result), (args, kwargs, result), ...), - hash : ((args, kwargs, result), ...), ... - } The execution result is returned as a tuple having this structure exec_result = ( (True, result, handler), # on success - (False, error_info, handler), # on error + (False, error_info, handler), # on error (None, None, handler), ... # asynchronous execution ) """ @@ -103,31 +105,34 @@ class Event(object): self.handlers = {} self.memoize = {} + def hash(self, handler): + return hashlib.md5(str(handler)).hexdigest() + def handle(self, handler, priority = 0): - """ Registers a handler. The handler can be transmitted together + """ Registers a handler. The handler can be transmitted together with two arguments as a list or dictionary. The arguments are: - - memoize + + memoize if True, the execution result will be cached in self.memoize - timeout + timeout will allocate a predefined time interval for the execution - - If arguments are provided as a list, they are considered to have - this sequence: (handler, memoize, timeout) - + + If arguments are provided as a list, they are considered to have + this sequence: (handler, memoize, timeout) + Examples: - event += handler + event += handler event += (handler, True, 1.5) - event += {'handler':handler, 'memoize':True, 'timeout':1.5} + event += {'handler':handler, 'memoize':True, 'timeout':1.5} """ handler_, memoize, timeout = self._extract(handler) - self.handlers['%s.%s' % (priority, hash(handler_))] = (handler_, memoize, timeout) + self.handlers['%s.%s' % (priority, self.hash(handler_))] = (handler_, memoize, timeout) return self def unhandle(self, handler): """ Unregisters a handler """ handler_, memoize, timeout = self._extract(handler) - key = hash(handler_) + key = self.hash(handler_) if not key in self.handlers: raise ValueError('Handler "%s" was not found' % str(handler_)) del self.handlers[key] @@ -139,6 +144,7 @@ class Event(object): self.result = [] if self.handlers: + max_threads = self._threads() for i in range(max_threads): @@ -172,10 +178,8 @@ class Event(object): """ Executes all handlers stored in the queue """ while True: try: - handler, memoize, timeout = self.handlers[self.queue.get()] - - if isinstance(self.lock, threading._RLock): - self.lock.acquire() #synchronization + h_ = self.queue.get() + handler, memoize, timeout = self.handlers[h_] try: r = self._memoize(memoize, timeout, handler, *args, **kwargs) @@ -189,14 +193,9 @@ class Event(object): else: self.error_handler(sys.exc_info()) finally: - if isinstance(self.lock, threading._RLock): - self.lock.release() if not self.asynchronous: - try: - self.queue.task_done() - except ValueError: - pass + self.queue.task_done() if self.queue.empty(): raise Queue.Empty @@ -205,9 +204,9 @@ class Event(object): break def _extract(self, queue_item): - """ Extracts a handler and handler's arguments that can be provided - as list or dictionary. If arguments are provided as list, they are - considered to have this sequence: (handler, memoize, timeout) + """ Extracts a handler and handler's arguments that can be provided + as list or dictionary. If arguments are provided as list, they are + considered to have this sequence: (handler, memoize, timeout) Examples: event += handler event += (handler, True, 1.5) @@ -234,11 +233,11 @@ class Event(object): return (handler, bool(memoize), float(timeout)) def _memoize(self, memoize, timeout, handler, *args, **kwargs): - """ Caches the execution result of successful executions - hash = hash(handler) - memoize = { - hash : ((args, kwargs, result), (args, kwargs, result), ...), - hash : ((args, kwargs, result), ...), ... + """ Caches the execution result of successful executions + hash = self.hash(handler) + memoize = { + hash : ((args, kwargs, result), (args, kwargs, result), ...), + hash : ((args, kwargs, result), ...), ... } """ if not isinstance(handler, Event) and self.sender is not None: @@ -247,7 +246,8 @@ class Event(object): if not memoize: if timeout <= 0: #no time restriction - return [True, handler(*args, **kwargs), handler] + result = [True, handler(*args, **kwargs), handler] + return result result = self._timeout(timeout, handler, *args, **kwargs) if isinstance(result, tuple) and len(result) == 3: @@ -255,7 +255,7 @@ class Event(object): return [False, self._error(result), handler] return [True, result, handler] else: - hash_ = hash(handler) + hash_ = self.hash(handler) if hash_ in self.memoize: for args_, kwargs_, result in self.memoize[hash_]: if args_ == args and kwargs_ == kwargs: