Switched event system (again)

This commit is contained in:
Ruud committed 2011-03-01 11:12:56 +01:00
1 parent fd5a41435b
commit f9523444f4
24 files changed
+580 -142

No files matched your search

+3
View File
@@ -1,5 +1,8 @@
from uuid import uuid4
def start():
pass
config = [{
'name': 'global',
'tab': 'general',
+1 -1
View File
@@ -15,7 +15,7 @@ def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
auth = getattr(request, 'authorization')
if Env.setting('username') and (not auth or not check_auth(auth.username, auth.password)):
return authenticate()
+37
View File
@@ -0,0 +1,37 @@
from couchpotato.core.logger import CPLog
from axl.axel import Event
log = CPLog(__name__)
events = {}
def addEvent(name, handler):
if events.get(name):
e = events[name]
else:
e = events[name] = Event(threads = 20, exc_info = True, traceback = True)
e += handler
def removeEvent(name, handler):
e = events[name]
e -= handler
def fireEvent(name, *args, **kwargs):
try:
e = events[name]
e.asynchronous = False
return e(*args, **kwargs)
except Exception, e:
log.debug(e)
def fireEventAsync(name, *args, **kwargs):
try:
e = events[name]
e.asynchronous = True
return e(*args, **kwargs)
except Exception, e:
log.debug(e)
def getEvent(name):
return events[name]
+37
View File
@@ -0,0 +1,37 @@
def latinToAscii(unicrap):
xlate = {0xc0:'A', 0xc1:'A', 0xc2:'A', 0xc3:'A', 0xc4:'A', 0xc5:'A',
0xc6:'Ae', 0xc7:'C',
0xc8:'E', 0xc9:'E', 0xca:'E', 0xcb:'E', 0x86:'e',
0xcc:'I', 0xcd:'I', 0xce:'I', 0xcf:'I',
0xd0:'Th', 0xd1:'N',
0xd2:'O', 0xd3:'O', 0xd4:'O', 0xd5:'O', 0xd6:'O', 0xd8:'O',
0xd9:'U', 0xda:'U', 0xdb:'U', 0xdc:'U',
0xdd:'Y', 0xde:'th', 0xdf:'ss',
0xe0:'a', 0xe1:'a', 0xe2:'a', 0xe3:'a', 0xe4:'a', 0xe5:'a',
0xe6:'ae', 0xe7:'c',
0xe8:'e', 0xe9:'e', 0xea:'e', 0xeb:'e',
0xec:'i', 0xed:'i', 0xee:'i', 0xef:'i',
0xf0:'th', 0xf1:'n',
0xf2:'o', 0xf3:'o', 0xf4:'o', 0xf5:'o', 0xf6:'o', 0xf8:'o',
0xf9:'u', 0xfa:'u', 0xfb:'u', 0xfc:'u',
0xfd:'y', 0xfe:'th', 0xff:'y',
0xa1:'!', 0xa2:'{cent}', 0xa3:'{pound}', 0xa4:'{currency}',
0xa5:'{yen}', 0xa6:'|', 0xa7:'{section}', 0xa8:'{umlaut}',
0xa9:'{C}', 0xaa:'{^a}', 0xab:'<<', 0xac:'{not}',
0xad:'-', 0xae:'{R}', 0xaf:'_', 0xb0:'{degrees}',
0xb1:'{+/-}', 0xb2:'{^2}', 0xb3:'{^3}', 0xb4:"'",
0xb5:'{micro}', 0xb6:'{paragraph}', 0xb7:'*', 0xb8:'{cedilla}',
0xb9:'{^1}', 0xba:'{^o}', 0xbb:'>>',
0xbc:'{1/4}', 0xbd:'{1/2}', 0xbe:'{3/4}', 0xbf:'?',
0xd7:'*', 0xf7:'/'
}
r = ''
for i in unicrap:
if xlate.has_key(ord(i)):
r += xlate[ord(i)]
elif ord(i) >= 0x80:
pass
else:
r += str(i)
return r
+86
View File
@@ -0,0 +1,86 @@
from couchpotato.core.event import fireEvent, fireEventAsync
from couchpotato.core.logger import CPLog
import glob
import os
log = CPLog(__name__)
class Loader:
plugins = {}
providers = {}
modules = {}
def preload(self, root = ''):
self.paths = {
'plugin' : ('couchpotato.core.plugins', os.path.join(root, 'couchpotato', 'core', 'plugins')),
'provider' : ('couchpotato.core.providers', os.path.join(root, 'couchpotato', 'core', 'providers')),
}
for type, tuple in self.paths.iteritems():
self.addFromDir(type, tuple[0], tuple[1])
def run(self):
did_save = 0
for module_name, plugin in sorted(self.modules.iteritems()):
# Load module
m = getattr(self.loadModule(module_name), plugin.get('name'))
log.info("Loading '%s'" % module_name)
# Save default settings for plugin/provider
did_save += self.loadSettings(m, module_name, save = False)
self.loadPlugins(m, plugin.get('name'))
if did_save:
fireEvent('settings.save')
def addFromDir(self, type, module, dir):
for file in glob.glob(os.path.join(dir, '*')):
name = os.path.basename(file)
if os.path.isdir(os.path.join(dir, name)):
module_name = '%s.%s' % (module, name)
self.addModule(type, module_name, name)
def loadSettings(self, module, name, save = True):
try:
for section in module.config:
fireEventAsync('settings.options', section['name'], section)
options = {}
for key, option in section['options'].iteritems():
options[key] = option['default']
fireEventAsync('settings.register', section_name = section['name'], options = options, save = save)
return True
except Exception, e:
log.debug("Failed loading settings for '%s': %s" % (name, e))
return False
def loadPlugins(self, module, name):
try:
module.start()
return True
except Exception, e:
log.debug("Failed loading plugin '%s': %s" % (name, e))
return False
def addModule(self, type, module, name):
self.modules[module] = {
'type': type,
'name': name,
}
def loadModule(self, name):
try:
m = __import__(name)
splitted = name.split('.')
for sub in splitted[1:-1]:
m = getattr(m, sub)
return m
except:
raise
+4
View File
@@ -0,0 +1,4 @@
class Plugin():
def __init__(self):
pass
@@ -0,0 +1,6 @@
from couchpotato.core.plugins.file_browser.main import FileBrowser
def start():
return FileBrowser()
config = []
@@ -0,0 +1,56 @@
from couchpotato.api import addApiView
from couchpotato.environment import Env
from flask.helpers import jsonify
import os
import string
if os.name == 'nt':
import win32file
class FileBrowser():
def __init__(self, path = '/'):
self.path = path
addApiView('directory.list', self.view)
def getDirectories(self):
# Return driveletters or root if path is empty
if self.path == '/' or not self.path:
if os.name == 'nt':
return self.getDriveLetters()
self.path = '/'
dirs = []
for f in os.listdir(self.path):
path = os.path.join(self.path, f)
if(os.path.isdir(path)):
dirs.append(path)
return dirs
def getFiles(self):
pass
def getDriveLetters(self):
driveletters = []
for drive in string.ascii_uppercase:
if win32file.GetDriveType(drive + ":") == win32file.DRIVE_FIXED:
driveletters.append(drive + ":")
return driveletters
def view(self):
try:
fb = FileBrowser(Env.getParam('path', '/'))
dirs = fb.getDirectories()
except:
dirs = []
return jsonify({
'empty': len(dirs) == 0,
'dirs': dirs,
})
@@ -0,0 +1,6 @@
from couchpotato.core.plugins.movie_add.main import MovieAdd
def start():
return MovieAdd()
config = []
@@ -0,0 +1,30 @@
from couchpotato.api import addApiView
from couchpotato.core.event import getEvent, fireEvent
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
from flask.helpers import jsonify
class MovieAdd(Plugin):
def __init__(self):
addApiView('movie.add.search', self.search)
def search(self):
a = Env.getParams()
print fireEvent('provider.movie.search', q = a.get('q'))
movies = [
{'id': 1, 'name': 'test'}
]
return jsonify({
'success': True,
'empty': len(movies) == 0,
'movies': movies,
})
def select(self):
pass
@@ -1,3 +1,6 @@
def start():
pass
config = [{
'name': 'Renamer',
'tab': 'renaming',
+57
View File
@@ -0,0 +1,57 @@
from couchpotato.core.helpers import latinToAscii
from couchpotato.core.logger import CPLog
from string import ascii_letters, digits
import re
import unicodedata
import xml.etree.ElementTree as XMLTree
log = CPLog(__name__)
class Provider():
type = None # movie, nzb, torrent, subtitle, trailer
timeout = 10 # Default timeout for url requests
def __init__(self):
pass
def toSaveString(self, string):
string = latinToAscii(string)
string = ''.join((c for c in unicodedata.normalize('NFD', unicode(string)) if unicodedata.category(c) != 'Mn'))
safe_chars = ascii_letters + digits + '_ -.,\':!?'
r = ''.join([char if char in safe_chars else ' ' for char in string])
return re.sub('\s+' , ' ', r)
def toSearchString(self, string):
string = latinToAscii(string)
string = ''.join((c for c in unicodedata.normalize('NFD', unicode(string)) if unicodedata.category(c) != 'Mn'))
safe_chars = ascii_letters + digits + ' \''
r = ''.join([char if char in safe_chars else ' ' for char in string])
return re.sub('\s+' , ' ', r).replace('\'s', 's').replace('\'', ' ')
def gettextelements(self, xml, path):
''' Find elements and return tree'''
textelements = []
try:
elements = xml.findall(path)
except:
return
for element in elements:
textelements.append(element.text)
return textelements
def gettextelement(self, xml, path):
''' Find element and return text'''
try:
return xml.find(path).text
except:
return
def getItems(self, data, path = 'channel/item'):
try:
return XMLTree.parse(data).findall(path)
except Exception, e:
log.error('Error parsing RSS. %s' % e)
return []
@@ -0,0 +1,4 @@
def start():
pass
config = []
+83
View File
@@ -0,0 +1,83 @@
from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.base import Provider
from imdb import IMDb
log = CPLog(__name__)
class IMDB(Provider):
type = 'movie'
def __init__(self):
addEvent('provider.movie.search', self.search)
self.p = IMDb('http')
def search(self):
print 'search'
def conf(self, option):
return self.config.get('IMDB', option)
def find(self, q, limit = 8, alternative = True):
''' Find movie by name '''
log.info('IMDB - Searching for movie: %s' % q)
r = self.p.search_movie(q)
return self.toResults(r, limit)
def toResults(self, r, limit = 8, one = False):
results = []
if one:
new = self.feedItem()
new.imdb = 'tt' + r.movieID
new.name = self.toSaveString(r['title'])
try:
new.year = r['year']
except:
new.year = ''
return new
else :
nr = 0
for movie in r:
results.append(self.toResults(movie, one = True))
nr += 1
if nr == limit:
break
return results
def findById(self, id):
''' Find movie by TheMovieDB ID '''
return []
def findByImdbId(self, id, details = False):
''' Find movie by IMDB ID '''
log.info('IMDB - Searching for movie: %s' % str(id))
r = self.p.get_movie(id.replace('tt', ''))
if not details:
return self.toResults(r, one = True)
else:
self.p.update(r)
self.p.update(r, info = 'release dates')
self.p.update(r, info = 'taglines')
return r
def get_IMDb_instance(self):
return IMDb('http')
def findReleaseDate(self, movie):
pass
@@ -1,3 +1,8 @@
from couchpotato.core.providers.tmdb.main import TMDB
def start():
return TMDB()
config = [{
'name': 'TheMovieDB',
'tab': 'providers',
+92
View File
@@ -0,0 +1,92 @@
from __future__ import with_statement
from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.base import Provider
from couchpotato.environment import Env
from urllib import quote_plus
import urllib2
log = CPLog(__name__)
class TMDB(Provider):
"""Api for theMovieDb"""
type = 'movie'
apiUrl = 'http://api.themoviedb.org/2.1'
imageUrl = 'http://hwcdn.themoviedb.org'
def __init__(self):
addEvent('provider.movie.search', self.search)
def conf(self, attr):
return Env.setting(attr, 'TheMovieDB')
def search(self, q, limit = 8, alternative = True):
''' Find movie by name '''
if self.isDisabled():
return False
log.debug('TheMovieDB - Searching for movie: %s' % q)
url = "%s/%s/%s/xml/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(self.toSearchString(q)))
log.info('Searching: %s' % url)
data = urllib2.urlopen(url)
return self.parseXML(data, limit, alternative = alternative)
def parseXML(self, data, limit, alternative = True):
if data:
log.debug('TheMovieDB - Parsing RSS')
try:
xml = self.getItems(data, 'movies/movie')
results = []
nr = 0
for movie in xml:
id = int(self.gettextelement(movie, "id"))
name = self.gettextelement(movie, "name")
imdb = self.gettextelement(movie, "imdb_id")
year = str(self.gettextelement(movie, "released"))[:4]
# 1900 is the same as None
if year == '1900':
year = 'None'
results.append({
'id': id,
'name': self.toSaveString(name),
'imdb': imdb,
'year': year
})
alternativeName = self.gettextelement(movie, "alternative_name")
if alternativeName and alternative:
if alternativeName.lower() != name.lower() and alternativeName.lower() != 'none' and alternativeName != None:
results.append({
'id': id,
'name': self.toSaveString(alternativeName),
'imdb': imdb,
'year': year
})
nr += 1
if nr == limit:
break
#log.info('TheMovieDB - Found: %s' % results)
return results
except SyntaxError:
log.error('TheMovieDB - Failed to parse XML response from TheMovieDb')
return False
def isDisabled(self):
if self.conf('api_key') == '':
log.error('TheMovieDB - No API key provided for TheMovieDB')
True
else:
False
+44 -12
View File
@@ -1,5 +1,7 @@
from __future__ import with_statement
from blinker import signal, Signal
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent
from flask.helpers import jsonify
import ConfigParser
import os.path
import time
@@ -7,10 +9,13 @@ import time
class Settings():
on_save = Signal()
on_register = Signal()
bool = {'true':True, 'false':False}
options = {}
def __init__(self):
addApiView('settings', self.view)
addApiView('settings.save', self.saveView)
def setFile(self, file):
self.file = file
@@ -21,7 +26,7 @@ class Settings():
from couchpotato.core.logger import CPLog
self.log = CPLog(__name__)
self.connectSignals()
self.connectEvents()
def parser(self):
return self.p
@@ -29,18 +34,17 @@ class Settings():
def sections(self):
return self.p.sections()
def connectSignals(self):
signal('settings.register').connect(self.registerDefaults)
signal('settings.save').connect(self.save)
def connectEvents(self):
addEvent('settings.options', self.addOptions)
addEvent('settings.register', self.registerDefaults)
addEvent('settings.save', self.save)
def registerDefaults(self, section_name, options = {}, save = True):
self.addSection(section_name)
for option, value in options.iteritems():
self.setDefault(section_name, option, value)
self.log.debug('Defaults for "%s": %s' % (section_name, options))
self.on_register.send(self)
if save:
self.save(self)
@@ -73,12 +77,11 @@ class Settings():
values[section][option_name] = option_value
return values
def save(self, caller = ''):
def save(self):
with open(self.file, 'wb') as configfile:
self.p.write(configfile)
self.log.debug('Saved settings')
self.on_save.send(self)
def addSection(self, section):
if not self.p.has_section(section):
@@ -94,3 +97,32 @@ class Settings():
return True
except ValueError:
return False
def addOptions(self, section_name, options):
self.options[section_name] = options
def getOptions(self):
return self.options
def view(self):
return jsonify({
'options': self.getOptions(),
'values': self.getValues()
})
def saveView(self):
a = Env.getParams()
section = a.get('section')
option = a.get('name')
value = a.get('value')
self.set(option, section, value)
self.save()
return jsonify({
'success': True,
})
-79
View File
@@ -1,79 +0,0 @@
from blinker import signal
from couchpotato.core.logger import CPLog
import glob
import os
log = CPLog(__name__)
class SettingsLoader:
configs = {}
sections = {}
def __init__(self):
self.settings_register = signal('settings.register')
self.settings_save = signal('settings.save')
def load(self, root = ''):
self.paths = {
'plugins' : ('couchpotato.core.plugins', os.path.join(root, 'couchpotato', 'core', 'plugins')),
'providers' : ('couchpotato.core.providers', os.path.join(root, 'couchpotato', 'core', 'providers')),
}
for type, tuple in self.paths.iteritems():
self.addFromDir(tuple[0], tuple[1])
def run(self):
did_save = 0
for module, plugin_name in self.configs.iteritems():
did_save += self.loadConfig(module, plugin_name, save = False)
if did_save:
self.settings_save.send()
def addFromDir(self, module, dir):
for file in glob.glob(os.path.join(dir, '*')):
name = os.path.basename(file)
if os.path.isdir(os.path.join(dir, name)):
self.addConfig(module, name)
def loadConfig(self, module, name, save = True):
module_name = '%s.%s' % (module, name)
try:
m = getattr(self.loadModule(module_name), name)
for section in m.config:
self.addSection(section['name'], section)
options = {}
for key, option in section['options'].iteritems():
options[key] = option['default']
self.settings_register.send(section['name'], options = options, save = save)
return True
except Exception, e:
log.error("Failed loading config for %s: %s" % (name, e))
return False
def addConfig(self, module, name):
self.configs[module] = name
def loadModule(self, name):
try:
m = __import__(name)
splitted = name.split('.')
for sub in splitted[1:-1]:
m = getattr(m, sub)
return m
except:
raise
def addSection(self, section_name, options):
self.sections[section_name] = options
def getSections(self):
return self.sections
settings_loader = SettingsLoader()