rewritting the dating optimization services management (refs #319)

This commit is contained in:
Romain Bignon 2010-11-11 01:11:55 +01:00
commit fcabbbe19f
5 changed files with 229 additions and 81 deletions

View file

@ -20,19 +20,64 @@ import sys
import weboob import weboob
from weboob.tools.application.repl import ReplApplication 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.dating import ICapDating, OptimizationNotFound
from weboob.capabilities.contact import Contact
__all__ = ['HaveSex'] __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): class HaveSex(ReplApplication):
APPNAME = 'havesex' APPNAME = 'havesex'
VERSION = '0.4' VERSION = '0.4'
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon' COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
STORAGE_FILENAME = 'dating.storage' STORAGE_FILENAME = 'dating.storage'
CONFIG = {'optimizations': ''} STORAGE = {'optims': {}}
CAPS = ICapDating CAPS = ICapDating
EXTRA_FORMATTERS = {'profile': ProfileFormatter}
COMMANDS_FORMATTERS = {'optim': 'table',
'profile': 'profile'}
def load_default_backends(self): def load_default_backends(self):
self.load_backends(ICapDating, storage=self.create_storage(self.STORAGE_FILENAME)) self.load_backends(ICapDating, storage=self.create_storage(self.STORAGE_FILENAME))
@ -42,11 +87,9 @@ class HaveSex(ReplApplication):
self.do('init_optimizations').wait() self.do('init_optimizations').wait()
optimizations = self.config.get('optimizations') optimizations = self.storage.get('optims')
if optimizations: for optim, backends in optimizations.iteritems():
optimizations_list = optimizations.strip().split(' ') self.optims('start', backends, optim, store=False)
if optimizations_list:
self.optims('Starting', 'start_optimization', optimizations_list)
return ReplApplication.main(self, argv) return ReplApplication.main(self, argv)
@ -58,75 +101,168 @@ class HaveSex(ReplApplication):
""" """
_id, backend_name = self.parse_id(id) _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 found = 0
for backend, contact in self.do('get_contact', _id, backends=backend_name): for backend, contact in self.do('get_contact', _id, backends=backend_name):
if contact: if contact:
print 'Nickname:', contact.name self.format(contact)
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')])
found = 1 found = 1
if not found: if not found:
self.logger.error(u'Profile not found') self.logger.error(u'Profile not found')
else:
self.flush()
return True return True
def service(self, action, function, *params): def edit_optims(self, backend_names, optims_names, stop=False):
sys.stdout.write('%s:' % action) for optim_name in optims_names.split():
for backend, result in self.do(function, *params): backends_optims = {}
if result: for backend, optim in self.do('get_optimization', optim_name, backends=backend_names):
sys.stdout.write(' ' + backend.name) if optim:
sys.stdout.flush() backends_optims[backend.name] = optim
sys.stdout.write('.\n') 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): was_running = optim.is_running()
for optim in optims: 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: 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: except weboob.core.CallErrors, errors:
for backend, error, backtrace in errors: for backend, error, backtrace in errors:
if isinstance(error, OptimizationNotFound): 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): return 0
"""
start OPTIMIZATION [OPTIMIZATION [...]]
Start optimization services. def complete_optim(self, text, line, *ignored):
""" args = line.split(' ')
self.optims('Starting', 'start_optimization', optims) 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

View file

@ -78,8 +78,8 @@ class AuMBackend(BaseBackend, ICapMessages, ICapMessagesPost, ICapDating, ICapCh
# ---- ICapDating methods --------------------- # ---- ICapDating methods ---------------------
def init_optimizations(self): def init_optimizations(self):
self.OPTIM_PROFILE_WALKER = ProfilesWalker(self.weboob.scheduler, self.storage, self.browser) self.add_optimization('PROFILE_WALKER', ProfilesWalker(self.weboob.scheduler, self.storage, self.browser))
self.OPTIM_VISIBILITY = Visibility(self.weboob.scheduler, self.browser) self.add_optimization('VISIBILITY', Visibility(self.weboob.scheduler, self.browser))
def get_status(self): def get_status(self):
with self.browser: with self.browser:

View file

@ -35,6 +35,8 @@ class ProfilesWalker(Optimization):
self.browser = browser self.browser = browser
self.logger = getLogger('walker', browser.logger) self.logger = getLogger('walker', browser.logger)
self.walk_cron = None
self.view_cron = None
self.visited_profiles = set(storage.get('profiles_walker', 'viewed')) 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.logger.info(u'Loaded %d already visited profiles from storage.' % len(self.visited_profiles))
self.profiles_queue = set() self.profiles_queue = set()
@ -54,6 +56,9 @@ class ProfilesWalker(Optimization):
# self.event = None # self.event = None
return False return False
def is_running(self):
return self.walk_cron is not None
def enqueue_profiles(self): def enqueue_profiles(self):
try: try:
with self.browser: with self.browser:

View file

@ -29,6 +29,7 @@ class Visibility(Optimization):
def __init__(self, sched, browser): def __init__(self, sched, browser):
self.sched = sched self.sched = sched
self.browser = browser self.browser = browser
self.cron = None
def start(self): def start(self):
self.cron = self.sched.repeat(60*5, self.reconnect) self.cron = self.sched.repeat(60*5, self.reconnect)
@ -38,6 +39,9 @@ class Visibility(Optimization):
# TODO # TODO
return False return False
def is_running(self):
return self.cron is not None
def reconnect(self): def reconnect(self):
try: try:
AuMBrowser(self.browser.username, AuMBrowser(self.browser.username,

View file

@ -27,12 +27,23 @@ class OptimizationNotFound(Exception):
class Optimization(object): class Optimization(object):
# Configuration of optim can be made by Value*s in this dict.
CONFIG = {}
def start(self): def start(self):
raise NotImplementedError() raise NotImplementedError()
def stop(self): def stop(self):
raise NotImplementedError() raise NotImplementedError()
def is_running(self):
raise NotImplementedError()
def get_config(self):
return None
def set_config(self, params):
raise NotImplementedError()
class StatusField(object): class StatusField(object):
FIELD_TEXT = 0x001 # the value is a long text FIELD_TEXT = 0x001 # the value is a long text
@ -52,33 +63,25 @@ class ICapDating(IBaseCap):
""" """
raise NotImplementedError() raise NotImplementedError()
OPTIM_PROFILE_WALKER = None
OPTIM_VISIBILITY = None
OPTIM_PRIORITY_CONNECTION = None
def init_optimizations(self): def init_optimizations(self):
raise NotImplementedError() 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() optim = optim.upper()
if not hasattr(self, 'OPTIM_%s' % optim): if not hasattr(self, 'OPTIM_%s' % optim):
raise OptimizationNotFound() raise OptimizationNotFound()
return getattr(self, 'OPTIM_%s' % optim) 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