diff --git a/weboob/applications/havesex/havesex.py b/weboob/applications/havesex/havesex.py index 90d48d08..3010bb1c 100644 --- a/weboob/applications/havesex/havesex.py +++ b/weboob/applications/havesex/havesex.py @@ -20,19 +20,64 @@ import sys import weboob from weboob.tools.application.repl import ReplApplication +from weboob.tools.application.formatters.iformatter import IFormatter from weboob.capabilities.dating import ICapDating, OptimizationNotFound +from weboob.capabilities.contact import Contact __all__ = ['HaveSex'] +class ProfileFormatter(IFormatter): + def flush(self): + pass + + def print_node(self, node, level=1): + result = u'' + if node.flags & node.SECTION: + result += u'\t' * level + node.label + '\n' + for sub in node.value: + result += self.print_node(sub, level+1) + else: + if isinstance(node.value, (tuple,list)): + value = ','.join([unicode(v) for v in node.value]) + else: + value = node.value + result += u'\t' * level + u'%-20s %s\n' % (node.label + ':', value) + return result + + def format_dict(self, item): + result = u'Nickname: %s\n' % item['name'] + if item['status'] & Contact.STATUS_ONLINE: + s = 'online' + elif item['status'] & Contact.STATUS_OFFLINE: + s = 'offline' + elif item['status'] & Contact.STATUS_AWAY: + s = 'away' + else: + s = 'unknown' + result += u'Status: %s (%s)\n' % (s, item['status_msg']) + result += u'Photos:\n' + for name, photo in item['photos'].iteritems(): + result += u'\t%s\n' % photo + result += u'Profile:\n' + for head in item['profile']: + result += self.print_node(head) + result += u'Description:\n' + for s in item['summary'].split('\n'): + result += u'\t%s\n' % s + return result + class HaveSex(ReplApplication): APPNAME = 'havesex' VERSION = '0.4' COPYRIGHT = 'Copyright(C) 2010 Romain Bignon' STORAGE_FILENAME = 'dating.storage' - CONFIG = {'optimizations': ''} + STORAGE = {'optims': {}} CAPS = ICapDating + EXTRA_FORMATTERS = {'profile': ProfileFormatter} + COMMANDS_FORMATTERS = {'optim': 'table', + 'profile': 'profile'} def load_default_backends(self): self.load_backends(ICapDating, storage=self.create_storage(self.STORAGE_FILENAME)) @@ -42,11 +87,9 @@ class HaveSex(ReplApplication): self.do('init_optimizations').wait() - optimizations = self.config.get('optimizations') - if optimizations: - optimizations_list = optimizations.strip().split(' ') - if optimizations_list: - self.optims('Starting', 'start_optimization', optimizations_list) + optimizations = self.storage.get('optims') + for optim, backends in optimizations.iteritems(): + self.optims('start', backends, optim, store=False) return ReplApplication.main(self, argv) @@ -58,75 +101,168 @@ class HaveSex(ReplApplication): """ _id, backend_name = self.parse_id(id) - def print_node(node, level=1): - if node.flags & node.SECTION: - print '\t' * level + node.label - for sub in node.value: - print_node(sub, level+1) - else: - if isinstance(node.value, (tuple,list)): - value = ','.join([unicode(v) for v in node.value]) - else: - value = node.value - print '\t' * level + '%-20s %s' % (node.label + ':', value) - found = 0 for backend, contact in self.do('get_contact', _id, backends=backend_name): if contact: - print 'Nickname:', contact.name - if contact.status & contact.STATUS_ONLINE: - s = 'online' - elif contact.status & contact.STATUS_OFFLINE: - s = 'offline' - elif contact.status & contact.STATUS_AWAY: - s = 'away' - else: - s = 'unknown' - print 'Status: %s (%s)' % (s, contact.status_msg) - print 'Photos:' - for name, photo in contact.photos.iteritems(): - print '\t%s' % photo - print 'Profile:' - for head in contact.profile: - print_node(head) - print 'Description:' - print '\n'.join(['\t%s' % s for s in contact.summary.split('\n')]) + self.format(contact) found = 1 if not found: self.logger.error(u'Profile not found') + else: + self.flush() return True - def service(self, action, function, *params): - sys.stdout.write('%s:' % action) - for backend, result in self.do(function, *params): - if result: - sys.stdout.write(' ' + backend.name) - sys.stdout.flush() - sys.stdout.write('.\n') + def edit_optims(self, backend_names, optims_names, stop=False): + for optim_name in optims_names.split(): + backends_optims = {} + for backend, optim in self.do('get_optimization', optim_name, backends=backend_names): + if optim: + backends_optims[backend.name] = optim + for backend_name, optim in backends_optims.iteritems(): + if len(optim.CONFIG) == 0: + print 'Nothing to do for %s.%s' % (backend_name, optim_name) + continue - def optims(self, action, function, optims): - for optim in optims: + was_running = optim.is_running() + if stop and was_running: + print 'Stopping %s: %s' % (optim_name, backend_name) + optim.stop() + params = optim.get_config() + if params is None: + params = {} + print 'Configuration of %s.%s' % (backend_name, optim_name) + print '-----------------%s.%s' % ('-' * len(backend_name), '-' * len(optim_name)) + for key, value in optim.CONFIG.iteritems(): + params[key] = self.ask(value, default=params[key] if (key in params) else value.default) + + optim.set_config(params) + if stop and was_running: + print 'Starting %s: %s' % (optim_name, backend_name) + optim.start() + + def optims(self, function, backend_names, optims, store=True): + if optims is None: + print >>sys.stderr, 'Error: missing parameters.' + return 1 + + for optim_name in optims.split(): try: - self.service('Starting "%s"' % optim, 'start_optimization', optim) + if store: + storage_optim = set(self.storage.get('optims', optim_name, default=[])) + sys.stdout.write('%sing %s:' % (function.capitalize(), optim_name)) + for backend, optim in self.do('get_optimization', optim_name, backends=backend_names): + if optim: + # It's useless to start a started optim, or to stop a stopped one. + if (function == 'start' and optim.is_running()) or \ + (function == 'stop' and not optim.is_running()): + continue + + # Optim is not configured and would be, ask user to do it. + if function == 'start' and len(optim.CONFIG) > 0 and optim.get_config() is None: + self.edit_optims(backend.name, optim_name) + + getattr(optim, function)() + sys.stdout.write(' ' + backend.name) + sys.stdout.flush() + if store: + if function == 'start': + storage_optim.add(backend.name) + elif function == 'stop': + try: + storage_optim.remove(backend.name) + except KeyError: + pass + sys.stdout.write('.\n') except weboob.core.CallErrors, errors: for backend, error, backtrace in errors: if isinstance(error, OptimizationNotFound): - self.logger.error(u'Optimization "%s" not found' % optim) + self.logger.error(u'Error(%s): Optimization "%s" not found' % (backend.name, optim_name)) + else: + self.logger.error(u'Error(%s): %s' % (backend.name, error)) + if logging.root.level == logging.DEBUG: + self.logger.error(backtrace) + if store: + if len(storage_optim) > 0: + self.storage.set('optims', optim_name, list(storage_optim)) + else: + self.storage.delete('optims', optim_name) + if store: + self.storage.save() - def do_start(self, *optims): - """ - start OPTIMIZATION [OPTIMIZATION [...]] + return 0 - Start optimization services. - """ - self.optims('Starting', 'start_optimization', optims) + def complete_optim(self, text, line, *ignored): + args = line.split(' ') + if len(args) == 2: + return ['list', 'start', 'stop', 'edit'] + elif len(args) == 3: + return [backend.name for backend in self.enabled_backends] + elif len(args) >= 4: + if args[2] == '*': + backend = None + else: + backend = args[2] + optims = set() + for backend, (name, optim) in self.do('iter_optimizations', backends=backend): + optims.add(name) + return sorted(optims - set(args[3:])) - def command_stop(self, *optims): + def do_optim(self, line): """ - stop OPTIMIZATION [OPTIMIZATION [...]] + optim [list | start | edit | stop] BACKEND [OPTIM1 [OPTIM2 ...]] - Stop optimization services. + All dating backends offer optimization services. This command can be + manage them. + Use * us BACKEND value to apply command to all backends. + + Commands: + * list list all available optimizations of a backend + * start start optimization services on a backend + * edit configure an optimization service for a backend + * stop stop optimization services on a backend """ - self.optims('Stopping', 'stop_optimization', optims) + cmd, backend_name, optims = self.parseargs(line, 3, 1) + + if backend_name == '*': + backend_name = None + elif backend_name is not None and not backend_name in [b.name for b in self.enabled_backends]: + print >>sys.stderr, 'Error: No such backend "%s"' % backend_name + return 1 + + if cmd == 'start': + return self.optims('start', backend_name, optims) + if cmd == 'stop': + return self.optims('stop', backend_name, optims) + if cmd == 'edit': + self.edit_optims(backend_name, optims, stop=True) + return + if cmd == 'list': + optims = {} + backends = set() + for backend, (name, optim) in self.do('iter_optimizations', backends=backend_name): + if optim.is_running(): + status = 'RUNNING' + else: + status = '-------' + if not name in optims: + optims[name] = {backend.name: status} + else: + optims[name][backend.name] = status + backends.add(backend.name) + + backends = sorted(backends) + for name, backends_status in optims.iteritems(): + line = [('name', name)] + for b in backends: + try: + status = backends_status[b] + except KeyError: + status = '' + line.append((b, status)) + self.format(tuple(line)) + self.flush() + return + print >>sys.stderr, "No such command '%s'" % cmd + return 1 diff --git a/weboob/backends/aum/backend.py b/weboob/backends/aum/backend.py index 4f731c63..4be55d50 100644 --- a/weboob/backends/aum/backend.py +++ b/weboob/backends/aum/backend.py @@ -78,8 +78,8 @@ class AuMBackend(BaseBackend, ICapMessages, ICapMessagesPost, ICapDating, ICapCh # ---- ICapDating methods --------------------- def init_optimizations(self): - self.OPTIM_PROFILE_WALKER = ProfilesWalker(self.weboob.scheduler, self.storage, self.browser) - self.OPTIM_VISIBILITY = Visibility(self.weboob.scheduler, self.browser) + self.add_optimization('PROFILE_WALKER', ProfilesWalker(self.weboob.scheduler, self.storage, self.browser)) + self.add_optimization('VISIBILITY', Visibility(self.weboob.scheduler, self.browser)) def get_status(self): with self.browser: diff --git a/weboob/backends/aum/optim/profiles_walker.py b/weboob/backends/aum/optim/profiles_walker.py index 5c961c88..9b049df9 100644 --- a/weboob/backends/aum/optim/profiles_walker.py +++ b/weboob/backends/aum/optim/profiles_walker.py @@ -35,6 +35,8 @@ class ProfilesWalker(Optimization): self.browser = browser self.logger = getLogger('walker', browser.logger) + self.walk_cron = None + self.view_cron = None self.visited_profiles = set(storage.get('profiles_walker', 'viewed')) self.logger.info(u'Loaded %d already visited profiles from storage.' % len(self.visited_profiles)) self.profiles_queue = set() @@ -54,6 +56,9 @@ class ProfilesWalker(Optimization): # self.event = None return False + def is_running(self): + return self.walk_cron is not None + def enqueue_profiles(self): try: with self.browser: diff --git a/weboob/backends/aum/optim/visibility.py b/weboob/backends/aum/optim/visibility.py index 8a9fa28e..76e5847e 100644 --- a/weboob/backends/aum/optim/visibility.py +++ b/weboob/backends/aum/optim/visibility.py @@ -29,6 +29,7 @@ class Visibility(Optimization): def __init__(self, sched, browser): self.sched = sched self.browser = browser + self.cron = None def start(self): self.cron = self.sched.repeat(60*5, self.reconnect) @@ -38,6 +39,9 @@ class Visibility(Optimization): # TODO return False + def is_running(self): + return self.cron is not None + def reconnect(self): try: AuMBrowser(self.browser.username, diff --git a/weboob/capabilities/dating.py b/weboob/capabilities/dating.py index cc9d24d5..1addab27 100644 --- a/weboob/capabilities/dating.py +++ b/weboob/capabilities/dating.py @@ -27,12 +27,23 @@ class OptimizationNotFound(Exception): class Optimization(object): + # Configuration of optim can be made by Value*s in this dict. + CONFIG = {} + def start(self): raise NotImplementedError() def stop(self): raise NotImplementedError() + def is_running(self): + raise NotImplementedError() + + def get_config(self): + return None + + def set_config(self, params): + raise NotImplementedError() class StatusField(object): FIELD_TEXT = 0x001 # the value is a long text @@ -52,33 +63,25 @@ class ICapDating(IBaseCap): """ raise NotImplementedError() - OPTIM_PROFILE_WALKER = None - OPTIM_VISIBILITY = None - OPTIM_PRIORITY_CONNECTION = None - def init_optimizations(self): raise NotImplementedError() - def _get_optim(self, optim): + def add_optimization(self, name, optim): + setattr(self, 'OPTIM_%s' % name, optim) + + def iter_optimizations(self, *optims): + for attr_name in dir(self): + if not attr_name.startswith('OPTIM_'): + continue + attr = getattr(self, attr_name) + if attr is None: + continue + + yield attr_name[6:], attr + + def get_optimization(self, optim): optim = optim.upper() if not hasattr(self, 'OPTIM_%s' % optim): raise OptimizationNotFound() return getattr(self, 'OPTIM_%s' % optim) - - def start_optimization(self, optim): - optim = self._get_optim(optim) - if not optim: - return False - - return optim.start() - - def stop_optimization(self, optim): - optim = self._get_optim(optim) - if not optim: - return False - - return optim.stop() - - def list_optimizations(self): - pass