25 changed files with 618 additions and 180 deletions
@ -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] |
@ -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 |
@ -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 |
@ -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,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 |
@ -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 = [] |
@ -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 |
@ -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 |
@ -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() |
|
Loading…
Reference in new issue