diff -r 058bb3dc685f -r 0b59724cb3f2 cubicweb/hooks/syncsources.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/hooks/syncsources.py Sat Jan 16 13:48:51 2016 +0100 @@ -0,0 +1,208 @@ +# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""hooks for repository sources synchronization""" + +from cubicweb import _ + +from socket import gethostname + +from logilab.common.decorators import clear_cache + +from cubicweb import validation_error +from cubicweb.predicates import is_instance +from cubicweb.server import SOURCE_TYPES, hook + +class SourceHook(hook.Hook): + __abstract__ = True + category = 'cw.sources' + + +# repo sources synchronization ################################################# + +class SourceAddedOp(hook.Operation): + entity = None # make pylint happy + def postcommit_event(self): + self.cnx.repo.add_source(self.entity) + +class SourceAddedHook(SourceHook): + __regid__ = 'cw.sources.added' + __select__ = SourceHook.__select__ & is_instance('CWSource') + events = ('after_add_entity',) + def __call__(self): + try: + sourcecls = SOURCE_TYPES[self.entity.type] + except KeyError: + msg = _('Unknown source type') + raise validation_error(self.entity, {('type', 'subject'): msg}) + # ignore creation of the system source done during database + # initialisation, as config for this source is in a file and handling + # is done separatly (no need for the operation either) + if self.entity.name != 'system': + sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config, + fail_if_unknown=not self._cw.vreg.config.repairing) + SourceAddedOp(self._cw, entity=self.entity) + + +class SourceRemovedOp(hook.Operation): + uri = None # make pylint happy + def postcommit_event(self): + self.cnx.repo.remove_source(self.uri) + +class SourceRemovedHook(SourceHook): + __regid__ = 'cw.sources.removed' + __select__ = SourceHook.__select__ & is_instance('CWSource') + events = ('before_delete_entity',) + def __call__(self): + if self.entity.name == 'system': + msg = _("You cannot remove the system source") + raise validation_error(self.entity, {None: msg}) + SourceRemovedOp(self._cw, uri=self.entity.name) + + +class SourceConfigUpdatedOp(hook.DataOperationMixIn, hook.Operation): + + def precommit_event(self): + self.__processed = [] + for source in self.get_data(): + if not self.cnx.deleted_in_transaction(source.eid): + conf = source.repo_source.check_config(source) + self.__processed.append( (source, conf) ) + + def postcommit_event(self): + for source, conf in self.__processed: + source.repo_source.update_config(source, conf) + + +class SourceRenamedOp(hook.LateOperation): + oldname = newname = None # make pylint happy + + def precommit_event(self): + source = self.cnx.repo.sources_by_uri[self.oldname] + sql = 'UPDATE entities SET asource=%(newname)s WHERE asource=%(oldname)s' + self.cnx.system_sql(sql, {'oldname': self.oldname, + 'newname': self.newname}) + + def postcommit_event(self): + repo = self.cnx.repo + # XXX race condition + source = repo.sources_by_uri.pop(self.oldname) + source.uri = self.newname + source.public_config['uri'] = self.newname + repo.sources_by_uri[self.newname] = source + repo._type_source_cache.clear() + clear_cache(repo, 'source_defs') + + +class SourceUpdatedHook(SourceHook): + __regid__ = 'cw.sources.configupdate' + __select__ = SourceHook.__select__ & is_instance('CWSource') + events = ('before_update_entity',) + def __call__(self): + if 'name' in self.entity.cw_edited: + oldname, newname = self.entity.cw_edited.oldnewvalue('name') + if oldname == 'system': + msg = _("You cannot rename the system source") + raise validation_error(self.entity, {('name', 'subject'): msg}) + SourceRenamedOp(self._cw, oldname=oldname, newname=newname) + if 'config' in self.entity.cw_edited or 'url' in self.entity.cw_edited: + if self.entity.name == 'system' and self.entity.config: + msg = _("Configuration of the system source goes to " + "the 'sources' file, not in the database") + raise validation_error(self.entity, {('config', 'subject'): msg}) + SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity) + + +class SourceHostConfigUpdatedHook(SourceHook): + __regid__ = 'cw.sources.hostconfigupdate' + __select__ = SourceHook.__select__ & is_instance('CWSourceHostConfig') + events = ('after_add_entity', 'after_update_entity', 'before_delete_entity',) + def __call__(self): + if self.entity.match(gethostname()): + if self.event == 'after_update_entity' and \ + not 'config' in self.entity.cw_edited: + return + try: + SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity.cwsource) + except IndexError: + # XXX no source linked to the host config yet + pass + + +# source mapping synchronization ############################################### +# +# Expect cw_for_source/cw_schema are immutable relations (i.e. can't change from +# a source or schema to another). + +class SourceMappingImmutableHook(SourceHook): + """check cw_for_source and cw_schema are immutable relations + + XXX empty delete perms would be enough? + """ + __regid__ = 'cw.sources.mapping.immutable' + __select__ = SourceHook.__select__ & hook.match_rtype('cw_for_source', 'cw_schema') + events = ('before_add_relation',) + def __call__(self): + if not self._cw.added_in_transaction(self.eidfrom): + msg = _("You can't change this relation") + raise validation_error(self.eidfrom, {self.rtype: msg}) + + +class SourceMappingChangedOp(hook.DataOperationMixIn, hook.Operation): + def check_or_update(self, checkonly): + cnx = self.cnx + # take care, can't call get_data() twice + try: + data = self.__data + except AttributeError: + data = self.__data = self.get_data() + for schemacfg, source in data: + if source is None: + source = schemacfg.cwsource.repo_source + if cnx.added_in_transaction(schemacfg.eid): + if not cnx.deleted_in_transaction(schemacfg.eid): + source.add_schema_config(schemacfg, checkonly=checkonly) + elif cnx.deleted_in_transaction(schemacfg.eid): + source.del_schema_config(schemacfg, checkonly=checkonly) + else: + source.update_schema_config(schemacfg, checkonly=checkonly) + + def precommit_event(self): + self.check_or_update(True) + + def postcommit_event(self): + self.check_or_update(False) + + +class SourceMappingChangedHook(SourceHook): + __regid__ = 'cw.sources.schemaconfig' + __select__ = SourceHook.__select__ & is_instance('CWSourceSchemaConfig') + events = ('after_add_entity', 'after_update_entity') + def __call__(self): + if self.event == 'after_add_entity' or ( + self.event == 'after_update_entity' and 'options' in self.entity.cw_edited): + SourceMappingChangedOp.get_instance(self._cw).add_data( + (self.entity, None) ) + +class SourceMappingDeleteHook(SourceHook): + __regid__ = 'cw.sources.delschemaconfig' + __select__ = SourceHook.__select__ & hook.match_rtype('cw_for_source') + events = ('before_delete_relation',) + def __call__(self): + SourceMappingChangedOp.get_instance(self._cw).add_data( + (self._cw.entity_from_eid(self.eidfrom), + self._cw.entity_from_eid(self.eidto).repo_source) )