support new config management and login callback in applications

This commit is contained in:
Romain Bignon 2011-05-21 10:28:03 +02:00
commit a255916fa3
3 changed files with 207 additions and 121 deletions

View file

@ -18,7 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>. # along with weboob. If not, see <http://www.gnu.org/licenses/>.
from copy import deepcopy from copy import copy
import getpass import getpass
import logging import logging
import sys import sys
@ -64,8 +64,12 @@ class ConsoleApplication(BaseApplication):
def __init__(self, option_parser=None): def __init__(self, option_parser=None):
BaseApplication.__init__(self, option_parser) BaseApplication.__init__(self, option_parser)
self.weboob.callbacks['login'] = self.login_cb
self.enabled_backends = set() self.enabled_backends = set()
def login_cb(self, backend_name, value):
return self.ask('[%s] Password' % backend_name, masked=True, default='', regexp=value.regexp)
def unload_backends(self, *args, **kwargs): def unload_backends(self, *args, **kwargs):
unloaded = self.weboob.unload_backends(*args, **kwargs) unloaded = self.weboob.unload_backends(*args, **kwargs)
for backend in unloaded.itervalues(): for backend in unloaded.itervalues():
@ -221,8 +225,8 @@ class ConsoleApplication(BaseApplication):
asked_config = True asked_config = True
print 'Configuration of new account %s' % website print 'Configuration of new account %s' % website
print '-----------------------------%s' % ('-' * len(website)) print '-----------------------------%s' % ('-' * len(website))
p = deepcopy(prop) p = copy(prop)
p.set_value(self.ask(prop, default=account.properties[key].value if (key in account.properties) else prop.default)) p.set(self.ask(prop, default=account.properties[key].get() if (key in account.properties) else prop.default))
account.properties[key] = p account.properties[key] = p
if asked_config: if asked_config:
print '-----------------------------%s' % ('-' * len(website)) print '-----------------------------%s' % ('-' * len(website))
@ -239,7 +243,7 @@ class ConsoleApplication(BaseApplication):
backend_config = {} backend_config = {}
for key, value in account.properties.iteritems(): for key, value in account.properties.iteritems():
if key in backend.config: if key in backend.config:
backend_config[key] = value.value backend_config[key] = value.get()
if ask_add and self.ask('Do you want to add the new register account?', default=True): if ask_add and self.ask('Do you want to add the new register account?', default=True):
return self.add_backend(name, backend_config, ask_register=False) return self.add_backend(name, backend_config, ask_register=False)
@ -253,9 +257,12 @@ class ConsoleApplication(BaseApplication):
if params is None: if params is None:
params = {} params = {}
backend = None
config = None
if not edit: if not edit:
try: try:
backend = self.weboob.modules_loader.get_or_load_module(name) backend = self.weboob.modules_loader.get_or_load_module(name)
config = backend.config
except ModuleLoadError: except ModuleLoadError:
backend = None backend = None
else: else:
@ -264,15 +271,17 @@ class ConsoleApplication(BaseApplication):
backend = self.weboob.modules_loader.get_or_load_module(bname) backend = self.weboob.modules_loader.get_or_load_module(bname)
except ModuleLoadError: except ModuleLoadError:
backend = None backend = None
items.update(params) else:
params = items items.update(params)
params = items
config = backend.config.load(self.weboob, bname, name, params, nofail=True)
if not backend: if not backend:
print >>sys.stderr, 'Backend "%s" does not exist. Hint: use the "backends" command.' % name print >>sys.stderr, 'Backend "%s" does not exist. Hint: use the "backends" command.' % name
return 1 return 1
# ask for params non-specified on command-line arguments # ask for params non-specified on command-line arguments
asked_config = False asked_config = False
for key, value in backend.config.iteritems(): for key, value in config.iteritems():
if not asked_config: if not asked_config:
asked_config = True asked_config = True
print 'Configuration of backend' print 'Configuration of backend'
@ -284,21 +293,23 @@ class ConsoleApplication(BaseApplication):
if asked_config: if asked_config:
print '------------------------' print '------------------------'
while not edit and self.weboob.backends_config.backend_exists(name):
print >>sys.stderr, 'Backend instance "%s" already exists in "%s"' % (name, self.weboob.backends_config.confpath)
if not self.ask('Add new instance of "%s" backend?' % backend.name, default=False):
return 1
name = self.ask('Please give new instance name (could be "%s_1")' % backend.name, regexp=r'^[\w\-_]+$')
try: try:
self.weboob.backends_config.add_backend(name, name, params, edit=edit) config = config.load(self.weboob, backend.name, name, params, nofail=True)
print 'Backend "%s" successfully %s.' % (name, 'updated' if edit else 'added') for key, value in params.iteritems():
config[key].set(value)
config.save(edit=edit)
print 'Backend "%s" successfully added.' % name
return name return name
except BackendAlreadyExists: except BackendAlreadyExists:
print >>sys.stderr, 'Backend "%s" is already configured in file "%s"' % (name, self.weboob.backends_config.confpath) print >>sys.stderr, 'Instance "%s" already exists.' % name
while self.ask('Add new instance of "%s" backend?' % name, default=False): return 1
new_name = self.ask('Please give new instance name (could be "%s_1")' % name, regexp=r'^[\w\-_]+$')
try:
self.weboob.backends_config.add_backend(new_name, name, params)
print 'Backend "%s" successfully added.' % new_name
return new_name
except BackendAlreadyExists:
print >>sys.stderr, 'Instance "%s" already exists for backend "%s".' % (new_name, name)
return 1
def ask(self, question, default=None, masked=False, regexp=None, choices=None): def ask(self, question, default=None, masked=False, regexp=None, choices=None):
""" """
@ -313,7 +324,7 @@ class ConsoleApplication(BaseApplication):
""" """
if isinstance(question, Value): if isinstance(question, Value):
v = deepcopy(question) v = copy(question)
if default: if default:
v.default = default v.default = default
if masked: if masked:
@ -388,13 +399,13 @@ class ConsoleApplication(BaseApplication):
line = aliases[line] line = aliases[line]
try: try:
v.set_value(line) v.set(line)
except ValueError, e: except ValueError, e:
print >>sys.stderr, 'Error: %s' % e print >>sys.stderr, 'Error: %s' % e
else: else:
break break
return v.value return v.get()
def bcall_error_handler(self, backend, error, backtrace): def bcall_error_handler(self, backend, error, backtrace):
""" """

View file

@ -62,13 +62,14 @@ class BackendCfg(QDialog):
self.ui.configuredBackendsList.header().setResizeMode(QHeaderView.ResizeToContents) self.ui.configuredBackendsList.header().setResizeMode(QHeaderView.ResizeToContents)
self.ui.configFrame.hide() self.ui.configFrame.hide()
self.icon_cache = {}
for name, backend in self.weboob.modules_loader.loaded.iteritems(): for name, backend in self.weboob.modules_loader.loaded.iteritems():
if not self.caps or backend.has_caps(*self.caps): if not self.caps or backend.has_caps(*self.caps):
item = QListWidgetItem(name.capitalize()) item = QListWidgetItem(name.capitalize())
if backend.icon_path: if backend.icon_path:
img = QImage(backend.icon_path) item.setIcon(self.get_icon_cache(backend.icon_path))
item.setIcon(QIcon(QPixmap.fromImage(img)))
self.ui.backendsList.addItem(item) self.ui.backendsList.addItem(item)
@ -86,6 +87,12 @@ class BackendCfg(QDialog):
self.connect(self.ui.configButtonBox, SIGNAL('accepted()'), self.acceptBackend) self.connect(self.ui.configButtonBox, SIGNAL('accepted()'), self.acceptBackend)
self.connect(self.ui.configButtonBox, SIGNAL('rejected()'), self.rejectBackend) self.connect(self.ui.configButtonBox, SIGNAL('rejected()'), self.rejectBackend)
def get_icon_cache(self, path):
if not path in self.icon_cache:
img = QImage(path)
self.icon_cache[path] = QIcon(QPixmap.fromImage(img))
return self.icon_cache[path]
def loadConfiguredBackendsList(self): def loadConfiguredBackendsList(self):
self.ui.configuredBackendsList.clear() self.ui.configuredBackendsList.clear()
for instance_name, name, params in self.weboob.backends_config.iter_backends(): for instance_name, name, params in self.weboob.backends_config.iter_backends():
@ -102,8 +109,7 @@ class BackendCfg(QDialog):
else Qt.Unchecked) else Qt.Unchecked)
if backend.icon_path: if backend.icon_path:
img = QImage(backend.icon_path) item.setIcon(0, self.get_icon_cache(backend.icon_path))
item.setIcon(0, QIcon(QPixmap.fromImage(img)))
self.ui.configuredBackendsList.addTopLevelItem(item) self.ui.configuredBackendsList.addTopLevelItem(item)
@ -159,22 +165,23 @@ class BackendCfg(QDialog):
self.ui.configFrame.hide() self.ui.configFrame.hide()
self.loadConfiguredBackendsList() self.loadConfiguredBackendsList()
def editBackend(self, bname=None): def editBackend(self, name=None):
self.ui.registerButton.hide() self.ui.registerButton.hide()
self.ui.configFrame.show() self.ui.configFrame.show()
if bname is not None: if name is not None:
mname, params = self.weboob.backends_config.get_backend(bname) bname, params = self.weboob.backends_config.get_backend(name)
items = self.ui.backendsList.findItems(mname, Qt.MatchFixedString) items = self.ui.backendsList.findItems(bname, Qt.MatchFixedString)
if not items: if not items:
warning('Backend not found') warning('Backend not found')
else: else:
self.ui.backendsList.setCurrentItem(items[0]) self.ui.backendsList.setCurrentItem(items[0])
self.ui.backendsList.setEnabled(False) self.ui.backendsList.setEnabled(False)
self.ui.nameEdit.setText(bname) self.ui.nameEdit.setText(name)
self.ui.nameEdit.setEnabled(False) self.ui.nameEdit.setEnabled(False)
if '_proxy' in params: if '_proxy' in params:
self.ui.proxyBox.setChecked(True) self.ui.proxyBox.setChecked(True)
self.ui.proxyEdit.setText(params.pop('_proxy')) self.ui.proxyEdit.setText(params.pop('_proxy'))
@ -184,13 +191,14 @@ class BackendCfg(QDialog):
params.pop('_enabled', None) params.pop('_enabled', None)
for key, value in params.iteritems(): backend = self.weboob.modules_loader.loaded[bname]
for key, value in backend.config.load(self.weboob, bname, name, params, nofail=True).iteritems():
try: try:
l, widget = self.config_widgets[key] l, widget = self.config_widgets[key]
except KeyError: except KeyError:
warning('Key "%s" is not found' % key) warning('Key "%s" is not found' % key)
else: else:
widget.set_data(value) widget.set_value(value)
else: else:
self.ui.nameEdit.clear() self.ui.nameEdit.clear()
self.ui.nameEdit.setEnabled(True) self.ui.nameEdit.setEnabled(True)
@ -199,69 +207,6 @@ class BackendCfg(QDialog):
self.ui.backendsList.setEnabled(True) self.ui.backendsList.setEnabled(True)
self.ui.backendsList.setCurrentRow(-1) self.ui.backendsList.setCurrentRow(-1)
def acceptBackend(self):
bname = unicode(self.ui.nameEdit.text())
selection = self.ui.backendsList.selectedItems()
if not selection:
QMessageBox.critical(self, self.tr('Unable to add a configured backend'),
self.tr('Please select a backend'))
return
try:
backend = self.weboob.modules_loader.get_or_load_module(unicode(selection[0].text()).lower())
except ModuleLoadError:
backend = None
if not backend:
QMessageBox.critical(self, self.tr('Unable to add a configured backend'),
self.tr('The selected backend does not exist.'))
return
params = {}
if not bname:
QMessageBox.critical(self, self.tr('Missing field'), self.tr('Please specify a backend name'))
return
if self.ui.nameEdit.isEnabled() and not re.match(r'^[\w\-_]+$', bname):
QMessageBox.critical(self, self.tr('Invalid value'),
self.tr('The backend name can only contain letters and digits'))
return
if self.ui.proxyBox.isChecked():
params['_proxy'] = unicode(self.ui.proxyEdit.text())
if not params['_proxy']:
QMessageBox.critical(self, self.tr('Missing field'), self.tr('Please specify a proxy URL'))
return
for key, field in backend.config.iteritems():
label, qtvalue = self.config_widgets[key]
try:
value = qtvalue.get_value()
except ValueError, e:
QMessageBox.critical(self, self.tr('Invalid value'),
unicode(self.tr('Invalid value for field "%s":<br /><br />%s')) % (field.label, e))
return
params[key] = value.value
try:
self.weboob.backends_config.add_backend(bname, backend.name, params, edit=not self.ui.nameEdit.isEnabled())
except BackendAlreadyExists, e:
QMessageBox.critical(self, self.tr('Unable to create backend'),
unicode(self.tr('Unable to create backend "%s": it already exists')) % bname)
return
self.to_load.add(bname)
self.ui.configFrame.hide()
self.loadConfiguredBackendsList()
def rejectBackend(self):
self.ui.configFrame.hide()
def backendSelectionChanged(self): def backendSelectionChanged(self):
for key, (label, value) in self.config_widgets.iteritems(): for key, (label, value) in self.config_widgets.iteritems():
label.hide() label.hide()
@ -288,6 +233,7 @@ class BackendCfg(QDialog):
self.ui.nameEdit.setText(backend.name) self.ui.nameEdit.setText(backend.name)
else: else:
self.ui.nameEdit.setText('') self.ui.nameEdit.setText('')
self.ui.backendInfo.setText(unicode(self.tr( self.ui.backendInfo.setText(unicode(self.tr(
'<h1>%s Backend %s</h1>' '<h1>%s Backend %s</h1>'
'<b>Version</b>: %s<br />' '<b>Version</b>: %s<br />'
@ -313,13 +259,82 @@ class BackendCfg(QDialog):
for key, field in backend.config.iteritems(): for key, field in backend.config.iteritems():
label = QLabel(u'%s:' % field.label) label = QLabel(u'%s:' % field.label)
value = QtValue(field) qvalue = QtValue(field)
self.ui.configLayout.addRow(label, value) self.ui.configLayout.addRow(label, qvalue)
self.config_widgets[key] = (label, value) self.config_widgets[key] = (label, qvalue)
def proxyEditEnabled(self, state): def proxyEditEnabled(self, state):
self.ui.proxyEdit.setEnabled(state) self.ui.proxyEdit.setEnabled(state)
def acceptBackend(self):
bname = unicode(self.ui.nameEdit.text())
selection = self.ui.backendsList.selectedItems()
if not selection:
QMessageBox.critical(self, self.tr('Unable to add a configured backend'),
self.tr('Please select a backend'))
return
try:
backend = self.weboob.modules_loader.get_or_load_module(unicode(selection[0].text()).lower())
except ModuleLoadError:
backend = None
if not backend:
QMessageBox.critical(self, self.tr('Unable to add a configured backend'),
self.tr('The selected backend does not exist.'))
return
params = {}
if not bname:
QMessageBox.critical(self, self.tr('Missing field'), self.tr('Please specify a backend name'))
return
if self.ui.nameEdit.isEnabled():
if not re.match(r'^[\w\-_]+$', bname):
QMessageBox.critical(self, self.tr('Invalid value'),
self.tr('The backend name can only contain letters and digits'))
return
if self.weboob.backends_config.backend_exists(bname):
QMessageBox.critical(self, self.tr('Unable to create backend'),
unicode(self.tr('Unable to create backend "%s": it already exists')) % bname)
return
if self.ui.proxyBox.isChecked():
params['_proxy'] = unicode(self.ui.proxyEdit.text())
if not params['_proxy']:
QMessageBox.critical(self, self.tr('Missing field'), self.tr('Please specify a proxy URL'))
return
config = backend.config.load(self.weboob, backend.name, bname, {}, nofail=True)
for key, field in config.iteritems():
label, qtvalue = self.config_widgets[key]
try:
value = qtvalue.get_value()
except ValueError, e:
QMessageBox.critical(self, self.tr('Invalid value'),
unicode(self.tr('Invalid value for field "%s":<br /><br />%s')) % (field.label, e))
return
field.set(value.get())
try:
config.save(edit=not self.ui.nameEdit.isEnabled(), params=params)
except BackendAlreadyExists:
QMessageBox.critical(self, self.tr('Unable to create backend'),
unicode(self.tr('Unable to create backend "%s": it already exists')) % bname)
return
self.to_load.add(bname)
self.ui.configFrame.hide()
self.loadConfiguredBackendsList()
def rejectBackend(self):
self.ui.configFrame.hide()
def registerEvent(self): def registerEvent(self):
selection = self.ui.backendsList.selectedItems() selection = self.ui.backendsList.selectedItems()
if not selection: if not selection:
@ -380,7 +395,7 @@ class BackendCfg(QDialog):
else: else:
for key, value in account.properties.iteritems(): for key, value in account.properties.iteritems():
if key in self.config_widgets: if key in self.config_widgets:
self.config_widgets[key][1].set_data(value.value) self.config_widgets[key][1].set_value(value)
def run(self): def run(self):
self.exec_() self.exec_()

View file

@ -20,16 +20,18 @@
import sys import sys
import logging import logging
import re import re
from copy import deepcopy from threading import Event
from PyQt4.QtCore import QTimer, SIGNAL, QObject, QString, QSize, QVariant from copy import copy
from PyQt4.QtCore import QTimer, SIGNAL, QObject, QString, QSize, QVariant, QMutex
from PyQt4.QtGui import QMainWindow, QApplication, QStyledItemDelegate, \ from PyQt4.QtGui import QMainWindow, QApplication, QStyledItemDelegate, \
QStyleOptionViewItemV4, QTextDocument, QStyle, \ QStyleOptionViewItemV4, QTextDocument, QStyle, \
QAbstractTextDocumentLayout, QPalette, QMessageBox, \ QAbstractTextDocumentLayout, QPalette, QMessageBox, \
QSpinBox, QLineEdit, QComboBox, QCheckBox QSpinBox, QLineEdit, QComboBox, QCheckBox, QInputDialog, \
QLineEdit
from weboob.core.ouiboube import Weboob from weboob.core.ouiboube import Weboob
from weboob.core.scheduler import IScheduler from weboob.core.scheduler import IScheduler
from weboob.tools.value import ValueInt, ValueBool from weboob.tools.value import ValueInt, ValueBool, ValueBackendPassword
from ..base import BaseApplication from ..base import BaseApplication
@ -77,12 +79,63 @@ class QtScheduler(IScheduler):
def run(self): def run(self):
self.app.exec_() self.app.exec_()
class QCallbacksManager(QObject):
class Request(object):
def __init__(self):
self.event = Event()
self.answer = None
def __call__(self):
raise NotImplementedError()
class LoginRequest(Request):
def __init__(self, backend_name, value):
QCallbacksManager.Request.__init__(self)
self.backend_name = backend_name
self.value = value
def __call__(self):
password, ok = QInputDialog.getText(None,
'Password request',
'Please enter password for %s' % self.backend_name,
QLineEdit.Password)
return password
def __init__(self, weboob, parent=None):
QObject.__init__(self, parent)
self.weboob = weboob
self.weboob.callbacks['login'] = self.callback(self.LoginRequest)
self.mutex = QMutex()
self.requests = []
self.connect(self, SIGNAL('new_request'), self.do_request)
def callback(self, klass):
def cb(*args, **kwargs):
return self.add_request(klass(*args, **kwargs))
return cb
def do_request(self):
self.mutex.lock()
request = self.requests.pop()
request.answer = request()
request.event.set()
self.mutex.unlock()
def add_request(self, request):
self.mutex.lock()
self.requests.append(request)
self.mutex.unlock()
self.emit(SIGNAL('new_request'))
request.event.wait()
return request.answer
class QtApplication(QApplication, BaseApplication): class QtApplication(QApplication, BaseApplication):
def __init__(self): def __init__(self):
QApplication.__init__(self, sys.argv) QApplication.__init__(self, sys.argv)
self.setApplicationName(self.APPNAME) self.setApplicationName(self.APPNAME)
BaseApplication.__init__(self) BaseApplication.__init__(self)
self.cbmanager = QCallbacksManager(self.weboob, self)
def create_weboob(self): def create_weboob(self):
return Weboob(scheduler=QtScheduler(self)) return Weboob(scheduler=QtScheduler(self))
@ -196,14 +249,19 @@ class _QtValueStr(QLineEdit):
if value.masked: if value.masked:
self.setEchoMode(self.Password) self.setEchoMode(self.Password)
def set_data(self, text): def set_value(self, value):
self._value.set_value(unicode(text)) self._value = value
self.setText(self._value.value) self.setText(self._value.get())
def get_value(self): def get_value(self):
self._value.set_value(unicode(self.text())) self._value.set(unicode(self.text()))
return self._value return self._value
class _QtValueBackendPassword(_QtValueStr):
def get_value(self):
self._value._domain = None
return _QtValueStr.get_value(self)
class _QtValueBool(QCheckBox): class _QtValueBool(QCheckBox):
def __init__(self, value): def __init__(self, value):
QCheckBox.__init__(self) QCheckBox.__init__(self)
@ -211,12 +269,12 @@ class _QtValueBool(QCheckBox):
if value.default: if value.default:
self.setChecked(True) self.setChecked(True)
def set_data(self, b): def set_value(self, value):
self._value.set_value(b) self._value = value
self.setChecked(self._value.value) self.setChecked(self._value.get())
def get_value(self): def get_value(self):
self._value.set_value(self.isChecked()) self._value.set(self.isChecked())
return self._value return self._value
class _QtValueInt(QSpinBox): class _QtValueInt(QSpinBox):
@ -226,12 +284,12 @@ class _QtValueInt(QSpinBox):
if value.default: if value.default:
self.setValue(int(value.default)) self.setValue(int(value.default))
def set_data(self, i): def set_value(self, value):
self._value.set_value(i) self._value = value
self.setValue(self._value.value) self.setValue(self._value.get())
def get_value(self): def get_value(self):
self._value.set_value(self.getValue()) self._value.set(self.getValue())
return self._value return self._value
class _QtValueChoices(QComboBox): class _QtValueChoices(QComboBox):
@ -243,15 +301,15 @@ class _QtValueChoices(QComboBox):
if value.default == k: if value.default == k:
self.setCurrentIndex(self.count()-1) self.setCurrentIndex(self.count()-1)
def set_data(self, c): def set_value(self, value):
self._value.set_value(c) self._value = value
for i in xrange(self.count()): for i in xrange(self.count()):
if unicode(self.itemData(i).toString()) == self._value.value: if unicode(self.itemData(i).toString()) == self._value.get():
self.setCurrentIndex(i) self.setCurrentIndex(i)
return return
def get_value(self): def get_value(self):
self._value.set_value(unicode(self.itemData(self.currentIndex()).toString())) self._value.set(unicode(self.itemData(self.currentIndex()).toString()))
return self._value return self._value
def QtValue(value): def QtValue(value):
@ -259,9 +317,11 @@ def QtValue(value):
klass = _QtValueBool klass = _QtValueBool
elif isinstance(value, ValueInt): elif isinstance(value, ValueInt):
klass = _QtValueInt klass = _QtValueInt
elif isinstance(value, ValueBackendPassword):
klass = _QtValueBackendPassword
elif value.choices is not None: elif value.choices is not None:
klass = _QtValueChoices klass = _QtValueChoices
else: else:
klass = _QtValueStr klass = _QtValueStr
return klass(deepcopy(value)) return klass(copy(value))