diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f95a8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/settings.conf +/logs/*.log \ No newline at end of file diff --git a/CouchPotato.py b/CouchPotato.py index 1a0c8cc..68cb79f 100755 --- a/CouchPotato.py +++ b/CouchPotato.py @@ -1,16 +1,21 @@ #!/usr/bin/env python """Wrapper for the command line interface.""" -import os from os.path import dirname, isfile +import os import subprocess import sys import traceback +# Root path +base_path = dirname(os.path.abspath(__file__)) + +# Insert local directories into path +sys.path.insert(0, os.path.join(base_path, 'libs')) try: from couchpotato import cli -except ImportError: +except ImportError, e: print "Checking local dependencies..." if isfile(__file__): cwd = dirname(__file__) @@ -31,12 +36,6 @@ except ImportError: print "=" * 78 print stderr print "=" * 78 - print "Registering libraries..." - # Insert local directories into path - lib_path = os.path.join(os.path.abspath(cwd), 'libs') - src_path = os.path.join(os.path.abspath(cwd), 'src') - sys.path.insert(0, lib_path) - sys.path.insert(0, src_path) print "Passing execution to couchpotato..." try: @@ -53,4 +52,4 @@ except ImportError: raise NotImplementedError("Don't know how to do that.") if __name__ == "__main__": - cli.cmd_couchpotato() + cli.cmd_couchpotato(base_path) diff --git a/couchpotato/cli.py b/couchpotato/cli.py index 0a4d423..8622b79 100644 --- a/couchpotato/cli.py +++ b/couchpotato/cli.py @@ -1,8 +1,54 @@ +from blinker import signal from couchpotato import app -import argparse +from couchpotato.settings import Settings +from logging import handlers +from optparse import OptionParser +import logging +import os.path +import sys -def cmd_couchpotato(): - """Commandline entry point.""" - # Make sure views are imported and registered. - import couchpotato.views - app.run(debug=True) + +def cmd_couchpotato(base_path): + '''Commandline entry point.''' + + # Options + parser = OptionParser('usage: %prog [options]') + parser.add_option('-l', '--logdir', dest = 'logdir', default = 'logs', help = 'log DIRECTORY (default ./logs)') + parser.add_option('-t', '--test', '--debug', action = 'store_true', dest = 'debug', help = 'Debug mode') + parser.add_option('-q', '--quiet', action = 'store_true', dest = 'quiet', help = "Don't log to console") + parser.add_option('-d', '--daemon', action = 'store_true', dest = 'daemon', help = 'Daemonize the app') + + (options, args) = parser.parse_args(sys.argv[1:]) + + + # Register settings + settings = Settings('settings.conf') + register = signal('settings_register') + register.connect(settings.registerDefaults) + + debug = options.debug or settings.get('environment') == 'development' + + # Logger + logger = logging.getLogger() + formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%H:%M:%S') + level = logging.DEBUG if debug else logging.INFO + logger.setLevel(level) + + # Output logging information to screen + if not options.quiet: + hdlr = logging.StreamHandler(sys.stderr) + hdlr.setFormatter(formatter) + logger.addHandler(hdlr) + + # Output logging information to file + hdlr2 = handlers.RotatingFileHandler(os.path.join(options.logdir, 'CouchPotato.log'), 'a', 5000000, 4) + hdlr2.setFormatter(formatter) + logger.addHandler(hdlr2) + + + # Load config + from couchpotato.settings.loader import SettingsLoader + SettingsLoader(root = base_path) + + # Create app + app.run(host = settings.get('host'), port = int(settings.get('port')), debug = debug) diff --git a/couchpotato/core/__init__.py b/couchpotato/core/__init__.py new file mode 100644 index 0000000..992b875 --- /dev/null +++ b/couchpotato/core/__init__.py @@ -0,0 +1,9 @@ +config = ('global', { + 'environment': 'production', + 'host': '0.0.0.0', + 'port': 5000, + 'username': '', + 'password': '', + 'launch_browser': True, + 'url_base': '', +}) diff --git a/couchpotato/core/logger.py b/couchpotato/core/logger.py new file mode 100644 index 0000000..43ae35b --- /dev/null +++ b/couchpotato/core/logger.py @@ -0,0 +1,21 @@ +from couchpotato import app + +class CPLog(): + + context = '' + + def __init__(self, context = ''): + self.context = context + self.logger = app.logger + + def info(self, msg): + self.logger.info(self.addContext(msg)) + + def debug(self, msg): + self.logger.debug(self.addContext(msg)) + + def error(self, msg): + self.logger.error(self.addContext(msg)) + + def addContext(self, msg): + return '[%+25.25s] %s' % (self.context[-25:], msg) diff --git a/couchpotato/core/plugins/__init__.py b/couchpotato/core/plugins/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/couchpotato/core/plugins/__init__.py @@ -0,0 +1 @@ + diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py new file mode 100644 index 0000000..8b3176f --- /dev/null +++ b/couchpotato/core/plugins/renamer/__init__.py @@ -0,0 +1,4 @@ +config = ('Renamer', { + 'enabled': False, + 'cleanup': False +}) diff --git a/couchpotato/core/providers/__init__.py b/couchpotato/core/providers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/couchpotato/core/providers/tmdb/__init__.py b/couchpotato/core/providers/tmdb/__init__.py new file mode 100644 index 0000000..432f4fe --- /dev/null +++ b/couchpotato/core/providers/tmdb/__init__.py @@ -0,0 +1,3 @@ +config = ('TheMovieDB', { + 'key': '9b939aee0aaafc12a65bf448e4af9543' +}) diff --git a/couchpotato/settings/__init__.py b/couchpotato/settings/__init__.py new file mode 100644 index 0000000..334afd3 --- /dev/null +++ b/couchpotato/settings/__init__.py @@ -0,0 +1,74 @@ +from __future__ import with_statement +from blinker import signal, Signal +from couchpotato.core.logger import CPLog +import ConfigParser +import time + +log = CPLog(__name__) +settings = signal('settings_register') + +class Settings(): + + on_save = Signal() + on_register = Signal() + + bool = {'true':True, 'false':False} + + def __init__(self, file): + self.file = file + + self.p = ConfigParser.RawConfigParser() + self.p.read(file) + + def parser(self): + return self.p + + def sections(self): + return self.s + + def registerDefaults(self, section_name, options): + + self.addSection(section_name) + for option, value in options.iteritems(): + self.setDefault(section_name, option, value) + + log.debug('Registered defaults %s: %s' % (section_name, options)) + self.on_register.send(self) + + self.save() + + def set(self, section, option, value): + return self.p.set(section, option, value) + + def get(self, option = '', section = 'global'): + value = self.p.get(section, option) + + if(self.is_int(value)): + return int(value) + + if str(value).lower() in self.bool: + return self.bool.get(str(value).lower()) + + return value if type(value) != str else value.strip() + + def is_int(self, value): + try: + int(value) + return True + except ValueError: + return False + + def save(self): + with open(self.file, 'wb') as configfile: + self.p.write(configfile) + + log.debug('Saved settings') + self.on_save.send(self) + + def addSection(self, section): + if not self.p.has_section(section): + self.p.add_section(section) + + def setDefault(self, section, option, value): + if not self.p.has_option(section, option): + self.p.set(section, option, value) diff --git a/couchpotato/db.py b/couchpotato/settings/db.py similarity index 100% rename from couchpotato/db.py rename to couchpotato/settings/db.py diff --git a/couchpotato/settings/loader.py b/couchpotato/settings/loader.py new file mode 100644 index 0000000..8b4879d --- /dev/null +++ b/couchpotato/settings/loader.py @@ -0,0 +1,47 @@ +from blinker import signal +from couchpotato.core.logger import CPLog +import glob +import os +import sys + +log = CPLog(__name__) + +class SettingsLoader: + + def __init__(self, root = ''): + + self.register = signal('settings_register') + + 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.loadFromDir(tuple[0], tuple[1]) + + def loadFromDir(self, module, dir): + for file in glob.glob(os.path.join(dir, '*')): + plugin_name = os.path.basename(file) + plugin_dir = os.path.join(dir, plugin_name) + if os.path.isdir(plugin_dir): + self.loadConfig(module, plugin_name) + + def loadConfig(self, module, name): + module_name = '%s.%s' % (module, name) + try: + m = getattr(self.loadModule(module_name), name) + (section, options) = m.config + self.register.send(section, options = options) + except: + log.error("Failed loading config for %s" % 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 diff --git a/couchpotato/model.py b/couchpotato/settings/model.py similarity index 87% rename from couchpotato/model.py rename to couchpotato/settings/model.py index bfb9295..37ec3d7 100644 --- a/couchpotato/model.py +++ b/couchpotato/settings/model.py @@ -1,7 +1,7 @@ -from sqlalchemy.orm import scoped_session, sessionmaker +from elixir import * from sqlalchemy import create_engine +from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.schema import ThreadLocalMetaData -from elixir import * # We would like to be able to create this schema in a specific database at # will, so we can test it easily. @@ -30,19 +30,19 @@ class Release(Entity): class File(Entity): """File that belongs to a release.""" history = OneToMany('RenameHistory') - path = Field(UnicodeString(255), nullable=False, unique=True) + path = Field(UnicodeString(255), nullable = False, unique = True) # Subtitles can have multiple parts, too part = Field(Integer) release = ManyToOne('Release') # Let's remember the size so we know about offline media. - size = Field(Integer, nullable=False) + size = Field(Integer, nullable = False) type = ManyToOne('FileType') class FileType(Entity): """Types could be trailer, subtitle, movie, partial movie etc.""" - identifier = Field(String(20), unique=True) - name = Field(UnicodeString(255), nullable=False) + identifier = Field(String(20), unique = True) + name = Field(UnicodeString(255), nullable = False) files = OneToMany('File') diff --git a/logs/__init__.py b/logs/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/logs/__init__.py @@ -0,0 +1 @@ +