130 return |
132 return |
131 open(backupfile,'w').close() # kinda lock |
133 open(backupfile,'w').close() # kinda lock |
132 os.chmod(backupfile, 0600) |
134 os.chmod(backupfile, 0600) |
133 # backup |
135 # backup |
134 tmpdir = tempfile.mkdtemp(dir=instbkdir) |
136 tmpdir = tempfile.mkdtemp(dir=instbkdir) |
135 for source in repo.sources: |
137 try: |
136 try: |
138 for source in repo.sources: |
137 source.backup(osp.join(tmpdir,source.uri)) |
139 try: |
138 except Exception, exc: |
140 source.backup(osp.join(tmpdir, source.uri)) |
139 print '-> error trying to backup [%s]' % exc |
141 except Exception, exc: |
140 if not self.confirm('Continue anyway?', default='n'): |
142 print '-> error trying to backup [%s]' % exc |
141 raise SystemExit(1) |
143 if not self.confirm('Continue anyway?', default='n'): |
142 bkup = tarfile.open(backupfile, 'w|gz') |
144 raise SystemExit(1) |
143 for filename in os.listdir(tmpdir): |
145 else: |
144 bkup.add(osp.join(tmpdir,filename), filename) |
146 break |
145 bkup.close() |
147 else: |
146 shutil.rmtree(tmpdir) |
148 bkup = tarfile.open(backupfile, 'w|gz') |
147 # call hooks |
149 for filename in os.listdir(tmpdir): |
148 repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp) |
150 bkup.add(osp.join(tmpdir,filename), filename) |
149 # done |
151 bkup.close() |
150 print '-> backup file', backupfile |
152 # call hooks |
|
153 repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp) |
|
154 # done |
|
155 print '-> backup file', backupfile |
|
156 finally: |
|
157 shutil.rmtree(tmpdir) |
151 |
158 |
152 def restore_database(self, backupfile, drop=True, systemonly=True, |
159 def restore_database(self, backupfile, drop=True, systemonly=True, |
153 askconfirm=True): |
160 askconfirm=True): |
154 config = self.config |
|
155 repo = self.repo_connect() |
|
156 # check |
161 # check |
157 if not osp.exists(backupfile): |
162 if not osp.exists(backupfile): |
158 raise Exception("Backup file %s doesn't exist" % backupfile) |
163 raise Exception("Backup file %s doesn't exist" % backupfile) |
159 return |
164 return |
160 if askconfirm and not self.confirm('Restore %s database from %s ?' |
165 if askconfirm and not self.confirm('Restore %s database from %s ?' |
161 % (config.appid, backupfile)): |
166 % (self.config.appid, backupfile)): |
162 return |
167 return |
163 # unpack backup |
168 # unpack backup |
164 bkup = tarfile.open(backupfile, 'r|gz') |
169 bkup = tarfile.open(backupfile, 'r|gz') |
165 for name in bkup.getnames(): |
170 for name in bkup.getnames(): |
166 if name[0] in '/.': |
171 if name[0] in '/.': |
167 raise Exception('Security check failed, path starts with "/" or "."') |
172 raise Exception('Security check failed, path starts with "/" or "."') |
168 bkup.close() # XXX seek error if not close+open !?! |
173 bkup.close() # XXX seek error if not close+open !?! |
169 bkup = tarfile.open(backupfile, 'r|gz') |
174 bkup = tarfile.open(backupfile, 'r|gz') |
170 tmpdir = tempfile.mkdtemp() |
175 tmpdir = tempfile.mkdtemp() |
171 bkup.extractall(path=tmpdir) |
176 bkup.extractall(path=tmpdir) |
|
177 |
|
178 self.config.open_connections_pools = False |
|
179 repo = self.repo_connect() |
172 for source in repo.sources: |
180 for source in repo.sources: |
173 if systemonly and source.uri != 'system': |
181 if systemonly and source.uri != 'system': |
174 continue |
182 continue |
175 try: |
183 try: |
176 source.restore(osp.join(tmpdir, source.uri), drop=drop) |
184 source.restore(osp.join(tmpdir, source.uri), drop=drop) |
413 self.rqlexec('SET X specializes Y WHERE X is CWEType, X name %(x)s, ' |
415 self.rqlexec('SET X specializes Y WHERE X is CWEType, X name %(x)s, ' |
414 'Y is CWEType, Y name %(y)s', |
416 'Y is CWEType, Y name %(y)s', |
415 {'x': str(repoeschema), 'y': str(espschema)}) |
417 {'x': str(repoeschema), 'y': str(espschema)}) |
416 self.rqlexecall(ss.updateeschema2rql(eschema), |
418 self.rqlexecall(ss.updateeschema2rql(eschema), |
417 ask_confirm=self.verbosity >= 2) |
419 ask_confirm=self.verbosity >= 2) |
418 for rschema, targettypes, x in eschema.relation_definitions(True): |
420 for rschema, targettypes, role in eschema.relation_definitions(True): |
419 if x == 'subject': |
421 if role == 'subject': |
420 if not rschema in repoeschema.subject_relations(): |
422 if not rschema in repoeschema.subject_relations(): |
421 continue |
423 continue |
422 subjtypes, objtypes = [etype], targettypes |
424 subjtypes, objtypes = [etype], targettypes |
423 else: # x == 'object' |
425 else: # role == 'object' |
424 if not rschema in repoeschema.object_relations(): |
426 if not rschema in repoeschema.object_relations(): |
425 continue |
427 continue |
426 subjtypes, objtypes = targettypes, [etype] |
428 subjtypes, objtypes = targettypes, [etype] |
427 self._synchronize_rschema(rschema, syncperms=syncperms, |
429 self._synchronize_rschema(rschema, syncperms=syncperms, |
428 syncrdefs=False) |
430 syncrdefs=False) |
641 # register entity's attributes |
648 # register entity's attributes |
642 for rschema, attrschema in eschema.attribute_definitions(): |
649 for rschema, attrschema in eschema.attribute_definitions(): |
643 # ignore those meta relations, they will be automatically added |
650 # ignore those meta relations, they will be automatically added |
644 if rschema.type in META_RTYPES: |
651 if rschema.type in META_RTYPES: |
645 continue |
652 continue |
646 if not rschema.type in applschema: |
653 if not rschema.type in instschema: |
647 # need to add the relation type and to commit to get it |
654 # need to add the relation type and to commit to get it |
648 # actually in the schema |
655 # actually in the schema |
649 self.cmd_add_relation_type(rschema.type, False, commit=True) |
656 self.cmd_add_relation_type(rschema.type, False, commit=True) |
650 # register relation definition |
657 # register relation definition |
651 self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type), |
658 self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type), |
652 ask_confirm=confirm) |
659 ask_confirm=confirm) |
|
660 # take care to newly introduced base class |
|
661 # XXX some part of this should probably be under the "if auto" block |
|
662 for spschema in eschema.specialized_by(recursive=False): |
|
663 try: |
|
664 instspschema = instschema[spschema] |
|
665 except KeyError: |
|
666 # specialized entity type not in schema, ignore |
|
667 continue |
|
668 if instspschema.specializes() != eschema: |
|
669 self.rqlexec('SET D specializes P WHERE D eid %(d)s, P name %(pn)s', |
|
670 {'d': instspschema.eid, |
|
671 'pn': eschema.type}, ask_confirm=confirm) |
|
672 for rschema, tschemas, role in spschema.relation_definitions(True): |
|
673 for tschema in tschemas: |
|
674 if not tschema in instschema: |
|
675 continue |
|
676 if role == 'subject': |
|
677 subjschema = spschema |
|
678 objschema = tschema |
|
679 if rschema.final and instspschema.has_subject_relation(rschema): |
|
680 # attribute already set, has_rdef would check if |
|
681 # it's of the same type, we don't want this so |
|
682 # simply skip here |
|
683 continue |
|
684 elif role == 'object': |
|
685 subjschema = tschema |
|
686 objschema = spschema |
|
687 if (rschema.rproperty(subjschema, objschema, 'infered') |
|
688 or (instschema.has_relation(rschema) and |
|
689 instschema[rschema].has_rdef(subjschema, objschema))): |
|
690 continue |
|
691 self.cmd_add_relation_definition( |
|
692 subjschema.type, rschema.type, objschema.type) |
653 if auto: |
693 if auto: |
654 # we have commit here to get relation types actually in the schema |
694 # we have commit here to get relation types actually in the schema |
655 self.commit() |
695 self.commit() |
656 added = [] |
696 added = [] |
657 for rschema in eschema.subject_relations(): |
697 for rschema in eschema.subject_relations(): |
658 # attribute relation have already been processed and |
698 # attribute relation have already been processed and |
659 # 'owned_by'/'created_by' will be automatically added |
699 # 'owned_by'/'created_by' will be automatically added |
660 if rschema.final or rschema.type in META_RTYPES: |
700 if rschema.final or rschema.type in META_RTYPES: |
661 continue |
701 continue |
662 rtypeadded = rschema.type in applschema |
702 rtypeadded = rschema.type in instschema |
663 for targetschema in rschema.objects(etype): |
703 for targetschema in rschema.objects(etype): |
664 # ignore relations where the targeted type is not in the |
704 # ignore relations where the targeted type is not in the |
665 # current instance schema |
705 # current instance schema |
666 targettype = targetschema.type |
706 targettype = targetschema.type |
667 if not targettype in applschema and targettype != etype: |
707 if not targettype in instschema and targettype != etype: |
668 continue |
708 continue |
669 if not rtypeadded: |
709 if not rtypeadded: |
670 # need to add the relation type and to commit to get it |
710 # need to add the relation type and to commit to get it |
671 # actually in the schema |
711 # actually in the schema |
672 added.append(rschema.type) |
712 added.append(rschema.type) |
677 # such as "Emailthread forked_from Emailthread" |
717 # such as "Emailthread forked_from Emailthread" |
678 added.append((etype, rschema.type, targettype)) |
718 added.append((etype, rschema.type, targettype)) |
679 self.rqlexecall(ss.rdef2rql(rschema, etype, targettype), |
719 self.rqlexecall(ss.rdef2rql(rschema, etype, targettype), |
680 ask_confirm=confirm) |
720 ask_confirm=confirm) |
681 for rschema in eschema.object_relations(): |
721 for rschema in eschema.object_relations(): |
682 rtypeadded = rschema.type in applschema or rschema.type in added |
722 rtypeadded = rschema.type in instschema or rschema.type in added |
683 for targetschema in rschema.subjects(etype): |
723 for targetschema in rschema.subjects(etype): |
684 # ignore relations where the targeted type is not in the |
724 # ignore relations where the targeted type is not in the |
685 # current instance schema |
725 # current instance schema |
686 targettype = targetschema.type |
726 targettype = targetschema.type |
687 # don't check targettype != etype since in this case the |
727 # don't check targettype != etype since in this case the |
688 # relation has already been added as a subject relation |
728 # relation has already been added as a subject relation |
689 if not targettype in applschema: |
729 if not targettype in instschema: |
690 continue |
730 continue |
691 if not rtypeadded: |
731 if not rtypeadded: |
692 # need to add the relation type and to commit to get it |
732 # need to add the relation type and to commit to get it |
693 # actually in the schema |
733 # actually in the schema |
694 self.cmd_add_relation_type(rschema.type, False, commit=True) |
734 self.cmd_add_relation_type(rschema.type, False, commit=True) |
912 def cmd_synchronize_permissions(self, ertype, commit=True): |
952 def cmd_synchronize_permissions(self, ertype, commit=True): |
913 self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit) |
953 self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit) |
914 |
954 |
915 # Workflows handling ###################################################### |
955 # Workflows handling ###################################################### |
916 |
956 |
|
957 def cmd_add_workflow(self, name, wfof, default=True, commit=False, |
|
958 **kwargs): |
|
959 self.session.set_pool() # ensure pool is set |
|
960 wf = self.cmd_create_entity('Workflow', name=unicode(name), |
|
961 **kwargs) |
|
962 if not isinstance(wfof, (list, tuple)): |
|
963 wfof = (wfof,) |
|
964 for etype in wfof: |
|
965 rset = self.rqlexec( |
|
966 'SET X workflow_of ET WHERE X eid %(x)s, ET name %(et)s', |
|
967 {'x': wf.eid, 'et': etype}, 'x', ask_confirm=False) |
|
968 assert rset, 'unexistant entity type %s' % etype |
|
969 if default: |
|
970 self.rqlexec( |
|
971 'SET ET default_workflow X WHERE X eid %(x)s, ET name %(et)s', |
|
972 {'x': wf.eid, 'et': etype}, 'x', ask_confirm=False) |
|
973 if commit: |
|
974 self.commit() |
|
975 return wf |
|
976 |
|
977 # XXX remove once cmd_add_[state|transition] are removed |
|
978 def _get_or_create_wf(self, etypes): |
|
979 self.session.set_pool() # ensure pool is set |
|
980 if not isinstance(etypes, (list, tuple)): |
|
981 etypes = (etypes,) |
|
982 rset = self.rqlexec('Workflow X WHERE X workflow_of ET, ET name %(et)s', |
|
983 {'et': etypes[0]}) |
|
984 if rset: |
|
985 return rset.get_entity(0, 0) |
|
986 return self.cmd_add_workflow('%s workflow' % ';'.join(etypes), etypes) |
|
987 |
|
988 @deprecated('use add_workflow and Workflow.add_state method') |
917 def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs): |
989 def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs): |
918 """method to ease workflow definition: add a state for one or more |
990 """method to ease workflow definition: add a state for one or more |
919 entity type(s) |
991 entity type(s) |
920 """ |
992 """ |
921 stateeid = self.cmd_add_entity('State', name=name, **kwargs) |
993 wf = self._get_or_create_wf(stateof) |
922 if not isinstance(stateof, (list, tuple)): |
994 state = wf.add_state(name, initial, **kwargs) |
923 stateof = (stateof,) |
995 if commit: |
924 for etype in stateof: |
996 self.commit() |
925 # XXX ensure etype validity |
997 return state.eid |
926 self.rqlexec('SET X state_of Y WHERE X eid %(x)s, Y name %(et)s', |
998 |
927 {'x': stateeid, 'et': etype}, 'x', ask_confirm=False) |
999 @deprecated('use add_workflow and Workflow.add_transition method') |
928 if initial: |
|
929 self.rqlexec('SET ET initial_state S WHERE ET name %(et)s, S eid %(x)s', |
|
930 {'x': stateeid, 'et': etype}, 'x', ask_confirm=False) |
|
931 if commit: |
|
932 self.commit() |
|
933 return stateeid |
|
934 |
|
935 def cmd_add_transition(self, name, transitionof, fromstates, tostate, |
1000 def cmd_add_transition(self, name, transitionof, fromstates, tostate, |
936 requiredgroups=(), conditions=(), commit=False, **kwargs): |
1001 requiredgroups=(), conditions=(), commit=False, **kwargs): |
937 """method to ease workflow definition: add a transition for one or more |
1002 """method to ease workflow definition: add a transition for one or more |
938 entity type(s), from one or more state and to a single state |
1003 entity type(s), from one or more state and to a single state |
939 """ |
1004 """ |
940 treid = self.cmd_add_entity('Transition', name=name, **kwargs) |
1005 wf = self._get_or_create_wf(transitionof) |
941 if not isinstance(transitionof, (list, tuple)): |
1006 tr = wf.add_transition(name, fromstates, tostate, requiredgroups, |
942 transitionof = (transitionof,) |
1007 conditions, **kwargs) |
943 for etype in transitionof: |
1008 if commit: |
944 # XXX ensure etype validity |
1009 self.commit() |
945 self.rqlexec('SET X transition_of Y WHERE X eid %(x)s, Y name %(et)s', |
1010 return tr.eid |
946 {'x': treid, 'et': etype}, 'x', ask_confirm=False) |
1011 |
947 for stateeid in fromstates: |
1012 @deprecated('use Transition.set_transition_permissions method') |
948 self.rqlexec('SET X allowed_transition Y WHERE X eid %(x)s, Y eid %(y)s', |
|
949 {'x': stateeid, 'y': treid}, 'x', ask_confirm=False) |
|
950 self.rqlexec('SET X destination_state Y WHERE X eid %(x)s, Y eid %(y)s', |
|
951 {'x': treid, 'y': tostate}, 'x', ask_confirm=False) |
|
952 self.cmd_set_transition_permissions(treid, requiredgroups, conditions, |
|
953 reset=False) |
|
954 if commit: |
|
955 self.commit() |
|
956 return treid |
|
957 |
|
958 def cmd_set_transition_permissions(self, treid, |
1013 def cmd_set_transition_permissions(self, treid, |
959 requiredgroups=(), conditions=(), |
1014 requiredgroups=(), conditions=(), |
960 reset=True, commit=False): |
1015 reset=True, commit=False): |
961 """set or add (if `reset` is False) groups and conditions for a |
1016 """set or add (if `reset` is False) groups and conditions for a |
962 transition |
1017 transition |
963 """ |
1018 """ |
964 if reset: |
1019 self.session.set_pool() # ensure pool is set |
965 self.rqlexec('DELETE T require_group G WHERE T eid %(x)s', |
1020 tr = self.session.entity_from_eid(treid) |
966 {'x': treid}, 'x', ask_confirm=False) |
1021 tr.set_transition_permissions(requiredgroups, conditions, reset) |
967 self.rqlexec('DELETE T condition R WHERE T eid %(x)s', |
1022 if commit: |
968 {'x': treid}, 'x', ask_confirm=False) |
1023 self.commit() |
969 for gname in requiredgroups: |
1024 |
970 ### XXX ensure gname validity |
1025 @deprecated('use entity.change_state("state")') |
971 self.rqlexec('SET T require_group G WHERE T eid %(x)s, G name %(gn)s', |
|
972 {'x': treid, 'gn': gname}, 'x', ask_confirm=False) |
|
973 if isinstance(conditions, basestring): |
|
974 conditions = (conditions,) |
|
975 for expr in conditions: |
|
976 if isinstance(expr, str): |
|
977 expr = unicode(expr) |
|
978 self.rqlexec('INSERT RQLExpression X: X exprtype "ERQLExpression", ' |
|
979 'X expression %(expr)s, T condition X ' |
|
980 'WHERE T eid %(x)s', |
|
981 {'x': treid, 'expr': expr}, 'x', ask_confirm=False) |
|
982 if commit: |
|
983 self.commit() |
|
984 |
|
985 def cmd_set_state(self, eid, statename, commit=False): |
1026 def cmd_set_state(self, eid, statename, commit=False): |
986 self.session.set_pool() # ensure pool is set |
1027 self.session.set_pool() # ensure pool is set |
987 entity = self.session.entity_from_eid(eid) |
1028 self.session.entity_from_eid(eid).change_state(statename) |
988 entity.change_state(entity.wf_state(statename).eid) |
|
989 if commit: |
1029 if commit: |
990 self.commit() |
1030 self.commit() |
991 |
1031 |
992 # CWProperty handling ###################################################### |
1032 # CWProperty handling ###################################################### |
993 |
1033 |
1000 value = unicode(value) |
1040 value = unicode(value) |
1001 try: |
1041 try: |
1002 prop = self.rqlexec('CWProperty X WHERE X pkey %(k)s', {'k': pkey}, |
1042 prop = self.rqlexec('CWProperty X WHERE X pkey %(k)s', {'k': pkey}, |
1003 ask_confirm=False).get_entity(0, 0) |
1043 ask_confirm=False).get_entity(0, 0) |
1004 except: |
1044 except: |
1005 self.cmd_add_entity('CWProperty', pkey=unicode(pkey), value=value) |
1045 self.cmd_create_entity('CWProperty', pkey=unicode(pkey), value=value) |
1006 else: |
1046 else: |
1007 self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s', |
1047 self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s', |
1008 {'k': pkey, 'v': value}, ask_confirm=False) |
1048 {'k': pkey, 'v': value}, ask_confirm=False) |
1009 |
1049 |
1010 # other data migration commands ########################################### |
1050 # other data migration commands ########################################### |
1011 |
1051 |
|
1052 def cmd_create_entity(self, etype, *args, **kwargs): |
|
1053 """add a new entity of the given type""" |
|
1054 commit = kwargs.pop('commit', False) |
|
1055 self.session.set_pool() |
|
1056 entity = self.session.create_entity(etype, *args, **kwargs) |
|
1057 if commit: |
|
1058 self.commit() |
|
1059 return entity |
|
1060 |
|
1061 @deprecated('use create_entity') |
1012 def cmd_add_entity(self, etype, *args, **kwargs): |
1062 def cmd_add_entity(self, etype, *args, **kwargs): |
1013 """add a new entity of the given type""" |
1063 """add a new entity of the given type""" |
1014 rql = 'INSERT %s X' % etype |
1064 return self.cmd_create_entity(etype, *args, **kwargs).eid |
1015 relations = [] |
|
1016 restrictions = [] |
|
1017 for rtype, rvar in args: |
|
1018 relations.append('X %s %s' % (rtype, rvar)) |
|
1019 restrictions.append('%s eid %s' % (rvar, kwargs.pop(rvar))) |
|
1020 commit = kwargs.pop('commit', False) |
|
1021 for attr in kwargs: |
|
1022 relations.append('X %s %%(%s)s' % (attr, attr)) |
|
1023 if relations: |
|
1024 rql = '%s: %s' % (rql, ', '.join(relations)) |
|
1025 if restrictions: |
|
1026 rql = '%s WHERE %s' % (rql, ', '.join(restrictions)) |
|
1027 eid = self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2).rows[0][0] |
|
1028 if commit: |
|
1029 self.commit() |
|
1030 return eid |
|
1031 |
1065 |
1032 def sqlexec(self, sql, args=None, ask_confirm=True): |
1066 def sqlexec(self, sql, args=None, ask_confirm=True): |
1033 """execute the given sql if confirmed |
1067 """execute the given sql if confirmed |
1034 |
1068 |
1035 should only be used for low level stuff undoable with existing higher |
1069 should only be used for low level stuff undoable with existing higher |