andrej@1511: #!/usr/bin/env python andrej@1511: # -*- coding: utf-8 -*- andrej@1511: andrej@1511: # This file is part of Beremiz, a Integrated Development Environment for andrej@1511: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. andrej@1511: # andrej@1511: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1511: # andrej@1511: # See COPYING file for copyrights details. andrej@1511: # andrej@1511: # This program is free software; you can redistribute it and/or andrej@1511: # modify it under the terms of the GNU General Public License andrej@1511: # as published by the Free Software Foundation; either version 2 andrej@1511: # of the License, or (at your option) any later version. andrej@1511: # andrej@1511: # This program is distributed in the hope that it will be useful, andrej@1511: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1511: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@1511: # GNU General Public License for more details. andrej@1511: # andrej@1511: # You should have received a copy of the GNU General Public License andrej@1511: # along with this program; if not, write to the Free Software andrej@1511: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. andrej@1511: etisserant@14: """ Edouard@725: Config Tree Node base class. Edouard@725: Edouard@725: - A Beremiz project is organized in a tree each node derivate from ConfigTreeNode Edouard@725: - Project tree organization match filesystem organization of project directory. Edouard@725: - Each node of the tree have its own xml configuration, whose grammar is defined for each node type, as XSD Edouard@725: - ... TODO : document etisserant@14: """ etisserant@14: kinsamanka@3750: andrej@1732: import os andrej@1732: import traceback andrej@1732: import types etisserant@14: import shutil Edouard@2523: from operator import add Edouard@2551: from functools import reduce andrej@2434: Laurent@1315: from lxml import etree Laurent@1315: Laurent@1315: from xmlclass import GenerateParserFromXSDstring andrej@1850: from PLCControler import LOCATION_CONFNODE Laurent@814: from editors.ConfTreeNodeEditor import ConfTreeNodeEditor edouard@2711: from POULibrary import UserAddressedException greg@274: Laurent@1315: _BaseParamsParser = GenerateParserFromXSDstring(""" etisserant@14: etisserant@14: etisserant@14: lbessard@86: lbessard@17: lbessard@86: etisserant@14: etisserant@14: Laurent@1315: """) etisserant@14: etisserant@14: NameTypeSeparator = '@' andrej@1722: XSDSchemaErrorMessage = _("{a1} XML file doesn't follow XSD schema at line {a2}:\n{a3}") etisserant@14: andrej@1736: andrej@1831: class ConfigTreeNode(object): etisserant@14: """ Edouard@717: This class is the one that define confnodes. etisserant@14: """ etisserant@14: etisserant@14: XSD = None Edouard@718: CTNChildrenTypes = [] Edouard@718: CTNMaxCount = None Edouard@717: ConfNodeMethods = [] greg@274: LibraryControler = None laurent@743: EditorType = ConfTreeNodeEditor laurent@738: IconPath = None andrej@1730: etisserant@14: def _AddParamsMembers(self): Edouard@718: self.CTNParams = None etisserant@29: if self.XSD: Laurent@1315: self.Parser = GenerateParserFromXSDstring(self.XSD) Laurent@1315: obj = self.Parser.CreateRoot() Laurent@1315: name = obj.getLocalTag() Laurent@1315: self.CTNParams = (name, obj) Laurent@1315: setattr(self, name, obj) lbessard@17: lbessard@17: def __init__(self): andrej@1730: # Create BaseParam Laurent@1315: self.BaseParams = _BaseParamsParser.CreateRoot() lbessard@17: self.MandatoryParams = ("BaseParams", self.BaseParams) etisserant@14: self._AddParamsMembers() Edouard@718: self.Children = {} laurent@656: self._View = None Edouard@717: # copy ConfNodeMethods so that it can be later customized Edouard@717: self.ConfNodeMethods = [dic.copy() for dic in self.ConfNodeMethods] andrej@1730: Edouard@718: def ConfNodeBaseXmlFilePath(self, CTNName=None): Edouard@718: return os.path.join(self.CTNPath(CTNName), "baseconfnode.xml") andrej@1730: Edouard@718: def ConfNodeXmlFilePath(self, CTNName=None): Edouard@718: return os.path.join(self.CTNPath(CTNName), "confnode.xml") Edouard@717: andrej@1740: def CTNPath(self, CTNName=None, project_path=None): Edouard@718: if not CTNName: Edouard@718: CTNName = self.CTNName() Laurent@1061: if not project_path: Laurent@1061: project_path = self.CTNParent.CTNPath() Laurent@1061: return os.path.join(project_path, Edouard@718: CTNName + NameTypeSeparator + self.CTNType) andrej@1730: Edouard@718: def CTNName(self): laurent@675: return self.BaseParams.getName() andrej@1730: Edouard@718: def CTNEnabled(self): laurent@675: return self.BaseParams.getEnabled() andrej@1730: Edouard@718: def CTNFullName(self): Edouard@718: parent = self.CTNParent.CTNFullName() laurent@656: if parent != "": Edouard@718: return parent + "." + self.CTNName() laurent@656: return self.BaseParams.getName() andrej@1730: Edouard@2523: def CTNSearch(self, criteria): Edouard@2523: # TODO match config's fields name and fields contents Edouard@2523: return reduce(add, [ Edouard@2523: CTNChild.CTNSearch(criteria) edouard@2579: for CTNChild in self.IterChildren()], []) Edouard@2523: laurent@781: def GetIconName(self): Edouard@734: return None andrej@1730: Edouard@718: def CTNTestModified(self): etisserant@118: return self.ChangesToSave etisserant@118: edouard@3674: def CTNMarkModified(self): edouard@3674: oldChangesToSave = self.ChangesToSave edouard@3674: self.ChangesToSave = True edouard@3674: if not oldChangesToSave: edouard@3674: appframe = self.GetCTRoot().AppFrame edouard@3674: if appframe is not None: edouard@3674: appframe.RefreshTitle() edouard@3674: appframe.RefreshPageTitles() edouard@4000: appframe.RefreshFileMenu() edouard@3674: etisserant@118: def ProjectTestModified(self): etisserant@118: """ etisserant@118: recursively check modified status etisserant@118: """ Edouard@718: if self.CTNTestModified(): etisserant@118: return True etisserant@118: Edouard@718: for CTNChild in self.IterChildren(): Edouard@718: if CTNChild.ProjectTestModified(): etisserant@118: return True etisserant@118: etisserant@14: return False andrej@1730: laurent@699: def RemoteExec(self, script, **kwargs): Edouard@718: return self.CTNParent.RemoteExec(script, **kwargs) andrej@1730: Laurent@1061: def OnCTNSave(self, from_project_path=None): andrej@1782: """Default, do nothing and return success""" etisserant@14: return True etisserant@14: andrej@1744: def GetParamsAttributes(self, path=None): lbessard@19: if path: lbessard@19: parts = path.split(".", 1) lbessard@19: if self.MandatoryParams and parts[0] == self.MandatoryParams[0]: lbessard@19: return self.MandatoryParams[1].getElementInfos(parts[0], parts[1]) Edouard@718: elif self.CTNParams and parts[0] == self.CTNParams[0]: Edouard@718: return self.CTNParams[1].getElementInfos(parts[0], parts[1]) lbessard@17: else: lbessard@19: params = [] Edouard@718: if self.CTNParams: Edouard@718: params.append(self.CTNParams[1].getElementInfos(self.CTNParams[0])) lbessard@19: return params andrej@1730: etisserant@203: def SetParamsAttribute(self, path, value): etisserant@118: self.ChangesToSave = True etisserant@29: # Filter IEC_Channel and Name, that have specific behavior etisserant@29: if path == "BaseParams.IEC_Channel": laurent@443: old_leading = ".".join(map(str, self.GetCurrentLocation())) laurent@443: new_value = self.FindNewIEC_Channel(value) Laurent@842: if new_value != value: Laurent@842: new_leading = ".".join(map(str, self.CTNParent.GetCurrentLocation() + (new_value,))) Laurent@842: self.GetCTRoot().UpdateProjectVariableLocation(old_leading, new_leading) laurent@443: return new_value, True etisserant@29: elif path == "BaseParams.Name": etisserant@203: res = self.FindNewName(value) Edouard@718: self.CTNRequestSave() etisserant@118: return res, True andrej@1730: lbessard@19: parts = path.split(".", 1) lbessard@19: if self.MandatoryParams and parts[0] == self.MandatoryParams[0]: lbessard@19: self.MandatoryParams[1].setElementValue(parts[1], value) Laurent@1179: value = self.MandatoryParams[1].getElementInfos(parts[0], parts[1])["value"] Edouard@718: elif self.CTNParams and parts[0] == self.CTNParams[0]: Edouard@718: self.CTNParams[1].setElementValue(parts[1], value) Laurent@1179: value = self.CTNParams[1].getElementInfos(parts[0], parts[1])["value"] etisserant@29: return value, False lbessard@17: Edouard@718: def CTNMakeDir(self): Edouard@718: os.mkdir(self.CTNPath()) Edouard@718: Laurent@1061: def CTNRequestSave(self, from_project_path=None): edouard@3318: if self.GetCTRoot().CheckProjectPathPerm(): Edouard@717: # If confnode do not have corresponding directory Edouard@718: ctnpath = self.CTNPath() Edouard@718: if not os.path.isdir(ctnpath): greg@427: # Create it Edouard@718: os.mkdir(ctnpath) andrej@1730: Edouard@717: # generate XML for base XML parameters controller of the confnode greg@427: if self.MandatoryParams: kinsamanka@3755: BaseXMLFile = open(self.ConfNodeBaseXmlFilePath(), 'w', encoding='utf-8') Laurent@1315: BaseXMLFile.write(etree.tostring( andrej@1730: self.MandatoryParams[1], andrej@1730: pretty_print=True, andrej@1730: xml_declaration=True, kinsamanka@3755: encoding='utf-8').decode()) greg@427: BaseXMLFile.close() andrej@1730: Edouard@717: # generate XML for XML parameters controller of the confnode Edouard@718: if self.CTNParams: kinsamanka@3755: XMLFile = open(self.ConfNodeXmlFilePath(), 'w', encoding='utf-8') Laurent@1315: XMLFile.write(etree.tostring( andrej@1730: self.CTNParams[1], andrej@1730: pretty_print=True, andrej@1730: xml_declaration=True, kinsamanka@3755: encoding='utf-8').decode()) greg@427: XMLFile.close() andrej@1730: Edouard@718: # Call the confnode specific OnCTNSave method Laurent@1061: result = self.OnCTNSave(from_project_path) greg@427: if not result: andrej@1734: return _("Error while saving \"%s\"\n") % self.CTNPath() andrej@1730: Edouard@717: # mark confnode as saved greg@427: self.ChangesToSave = False Edouard@718: # go through all children and do the same Edouard@718: for CTNChild in self.IterChildren(): Laurent@1063: CTNChildPath = None Laurent@1063: if from_project_path is not None: Laurent@1063: CTNChildPath = CTNChild.CTNPath(project_path=from_project_path) Laurent@1063: result = CTNChild.CTNRequestSave(CTNChildPath) greg@427: if result: greg@427: return result lbessard@17: return None andrej@1730: Edouard@718: def CTNImport(self, src_CTNPath): Edouard@718: shutil.copytree(src_CTNPath, self.CTNPath) etisserant@14: return True etisserant@14: Laurent@883: def CTNGlobalInstances(self): Laurent@883: """ Laurent@883: @return: [(instance_name, instance_type),...] Laurent@883: """ Laurent@883: return [] andrej@1730: Laurent@883: def _GlobalInstances(self): Laurent@883: instances = self.CTNGlobalInstances() Laurent@883: for CTNChild in self.IECSortedChildren(): Laurent@883: instances.extend(CTNChild._GlobalInstances()) Laurent@883: return instances andrej@1730: Edouard@718: def CTNGenerate_C(self, buildpath, locations): etisserant@14: """ etisserant@14: Generate C code etisserant@14: @param locations: List of complete variables locations \ etisserant@22: [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) etisserant@22: "NAME" : name of the variable (generally "__IW0_1_2" style) etisserant@22: "DIR" : direction "Q","I" or "M" etisserant@22: "SIZE" : size "X", "B", "W", "D", "L" etisserant@22: "LOC" : tuple of interger for IEC location (0,1,2,...) etisserant@22: }, ...] etisserant@18: @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND etisserant@18: """ andrej@1833: self.GetCTRoot().logger.write_warning(".".join(map(str, self.GetCurrentLocation())) + " -> Nothing to do\n") andrej@1740: return [], "", False andrej@1730: etisserant@203: def _Generate_C(self, buildpath, locations): Edouard@717: # Generate confnodes [(Cfiles, CFLAGS)], LDFLAGS, DoCalls, extra_files etisserant@203: # extra_files = [(fname,fobject), ...] Edouard@718: gen_result = self.CTNGenerate_C(buildpath, locations) Edouard@718: CTNCFilesAndCFLAGS, CTNLDFLAGS, DoCalls = gen_result[:3] etisserant@203: extra_files = gen_result[3:] laurent@361: # if some files have been generated put them in the list with their location Edouard@718: if CTNCFilesAndCFLAGS: Edouard@718: LocationCFilesAndCFLAGS = [(self.GetCurrentLocation(), CTNCFilesAndCFLAGS, DoCalls)] etisserant@47: else: etisserant@47: LocationCFilesAndCFLAGS = [] etisserant@47: Edouard@717: # confnode asks for some LDFLAGS Edouard@2003: LDFLAGS = [] Edouard@2003: if CTNLDFLAGS is not None: etisserant@47: # LDFLAGS can be either string kinsamanka@3752: if isinstance(CTNLDFLAGS, str): Edouard@2003: LDFLAGS += [CTNLDFLAGS] andrej@1733: # or list of strings andrej@1778: elif isinstance(CTNLDFLAGS, list): Edouard@2003: LDFLAGS += CTNLDFLAGS andrej@1730: Edouard@718: # recurse through all children, and stack their results Edouard@718: for CTNChild in self.IECSortedChildren(): Edouard@718: new_location = CTNChild.GetCurrentLocation() etisserant@24: # How deep are we in the tree ? andrej@1742: depth = len(new_location) etisserant@203: _LocationCFilesAndCFLAGS, _LDFLAGS, _extra_files = \ Edouard@718: CTNChild._Generate_C( andrej@1733: # keep the same path etisserant@14: buildpath, etisserant@14: # filter locations that start with current IEC location andrej@1746: [loc for loc in locations if loc["LOC"][0:depth] == new_location]) etisserant@14: # stack the result etisserant@47: LocationCFilesAndCFLAGS += _LocationCFilesAndCFLAGS etisserant@47: LDFLAGS += _LDFLAGS etisserant@203: extra_files += _extra_files andrej@1730: etisserant@203: return LocationCFilesAndCFLAGS, LDFLAGS, extra_files etisserant@14: Edouard@718: def IterChildren(self): kinsamanka@3750: for _CTNType, Children in list(self.Children.items()): Edouard@718: for CTNInstance in Children: Edouard@718: yield CTNInstance andrej@1730: Edouard@718: def IECSortedChildren(self): Edouard@718: # reorder children by IEC_channels andrej@1740: ordered = [(chld.BaseParams.getIEC_Channel(), chld) for chld in self.IterChildren()] etisserant@47: if ordered: etisserant@47: ordered.sort() kinsamanka@3750: return list(zip(*ordered))[1] etisserant@47: else: etisserant@47: return [] andrej@1730: etisserant@47: def _GetChildBySomething(self, something, toks): Edouard@718: for CTNInstance in self.IterChildren(): etisserant@14: # if match component of the name Edouard@718: if getattr(CTNInstance.BaseParams, something) == toks[0]: etisserant@14: # if Name have other components etisserant@47: if len(toks) >= 2: etisserant@14: # Recurse in order to find the latest object andrej@1747: return CTNInstance._GetChildBySomething(something, toks[1:]) etisserant@14: # No sub name -> found Edouard@718: return CTNInstance etisserant@14: # Not found etisserant@14: return None etisserant@14: etisserant@14: def GetChildByName(self, Name): etisserant@47: if Name: etisserant@47: toks = Name.split('.') etisserant@47: return self._GetChildBySomething("Name", toks) etisserant@47: else: etisserant@47: return self etisserant@14: etisserant@14: def GetChildByIECLocation(self, Location): etisserant@47: if Location: etisserant@47: return self._GetChildBySomething("IEC_Channel", Location) etisserant@47: else: etisserant@47: return self andrej@1730: etisserant@23: def GetCurrentLocation(self): etisserant@24: """ Edouard@717: @return: Tupple containing confnode IEC location of current confnode : %I0.0.4.5 => (0,0,4,5) etisserant@24: """ Edouard@718: return self.CTNParent.GetCurrentLocation() + (self.BaseParams.getIEC_Channel(),) etisserant@23: etisserant@47: def GetCurrentName(self): etisserant@47: """ etisserant@47: @return: String "ParentParentName.ParentName.Name" etisserant@47: """ andrej@1738: return self.CTNParent._GetCurrentName() + self.BaseParams.getName() etisserant@47: etisserant@47: def _GetCurrentName(self): etisserant@47: """ etisserant@47: @return: String "ParentParentName.ParentName.Name." etisserant@47: """ andrej@1738: return self.CTNParent._GetCurrentName() + self.BaseParams.getName() + "." Edouard@718: Edouard@718: def GetCTRoot(self): Edouard@718: return self.CTNParent.GetCTRoot() etisserant@23: lbessard@97: def GetFullIEC_Channel(self): lbessard@97: return ".".join([str(i) for i in self.GetCurrentLocation()]) + ".x" lbessard@97: lbessard@97: def GetLocations(self): lbessard@97: location = self.GetCurrentLocation() Edouard@718: return [loc for loc in self.CTNParent.GetLocations() if loc["LOC"][0:len(location)] == location] lbessard@97: laurent@401: def GetVariableLocationTree(self): laurent@401: ''' Edouard@717: This function is meant to be overridden by confnodes. laurent@401: laurent@401: It should returns an list of dictionaries andrej@1730: laurent@401: - IEC_type is an IEC type like BOOL/BYTE/SINT/... laurent@401: - location is a string of this variable's location, like "%IX0.0.0" laurent@401: ''' laurent@401: children = [] Edouard@718: for child in self.IECSortedChildren(): laurent@402: children.append(child.GetVariableLocationTree()) laurent@402: return {"name": self.BaseParams.getName(), Edouard@717: "type": LOCATION_CONFNODE, laurent@402: "location": self.GetFullIEC_Channel(), laurent@402: "children": children} laurent@401: etisserant@203: def FindNewName(self, DesiredName): etisserant@29: """ etisserant@29: Changes Name to DesiredName if available, Name-N if not. etisserant@29: @param DesiredName: The desired Name (string) etisserant@29: """ andrej@1846: Edouard@718: # Build a list of used Name out of parent's Children andrej@1742: AllNames = [] Edouard@718: for CTNInstance in self.CTNParent.IterChildren(): Edouard@718: if CTNInstance != self: Edouard@718: AllNames.append(CTNInstance.BaseParams.getName()) etisserant@29: etisserant@29: # Find a free name, eventually appending digit etisserant@29: res = DesiredName laurent@833: if DesiredName.endswith("_0"): Laurent@841: BaseDesiredName = DesiredName[:-2] Laurent@841: else: Laurent@841: BaseDesiredName = DesiredName etisserant@29: suffix = 1 etisserant@29: while res in AllNames: andrej@1734: res = "%s_%d" % (BaseDesiredName, suffix) etisserant@29: suffix += 1 andrej@1730: Edouard@717: # Check previous confnode existance etisserant@29: dontexist = self.BaseParams.getName() == "__unnamed__" Edouard@1932: if not dontexist: Edouard@1932: # Get old path Edouard@1932: oldpath = self.CTNPath() etisserant@29: # Set the new name etisserant@29: self.BaseParams.setName(res) Edouard@717: # Rename confnode dir if exist etisserant@29: if not dontexist: Edouard@1932: shutil.move(oldpath, self.CTNPath()) etisserant@29: # warn user he has two left hands etisserant@29: if DesiredName != res: andrej@1744: msg = _("A child named \"{a1}\" already exists -> \"{a2}\"\n").format(a1=DesiredName, a2=res) andrej@1581: self.GetCTRoot().logger.write_warning(msg) etisserant@29: return res etisserant@29: laurent@683: def GetAllChannels(self): andrej@1742: AllChannels = [] Edouard@718: for CTNInstance in self.CTNParent.IterChildren(): Edouard@718: if CTNInstance != self: Edouard@718: AllChannels.append(CTNInstance.BaseParams.getIEC_Channel()) laurent@683: AllChannels.sort() laurent@683: return AllChannels laurent@683: etisserant@203: def FindNewIEC_Channel(self, DesiredChannel): etisserant@14: """ etisserant@14: Changes IEC Channel number to DesiredChannel if available, nearest available if not. etisserant@14: @param DesiredChannel: The desired IEC channel (int) etisserant@14: """ etisserant@14: # Get Current IEC channel etisserant@14: CurrentChannel = self.BaseParams.getIEC_Channel() etisserant@14: # Do nothing if no change andrej@1782: # if CurrentChannel == DesiredChannel: return CurrentChannel Edouard@718: # Build a list of used Channels out of parent's Children laurent@683: AllChannels = self.GetAllChannels() andrej@1730: etisserant@14: # Now, try to guess the nearest available channel etisserant@14: res = DesiredChannel andrej@1737: while res in AllChannels: # While channel not free andrej@1737: if res < CurrentChannel: # Want to go down ? andrej@1758: res -= 1 # Test for n-1 andrej@1739: if res < 0: andrej@1734: self.GetCTRoot().logger.write_warning(_("Cannot find lower free IEC channel than %d\n") % CurrentChannel) andrej@1737: return CurrentChannel # Can't go bellow 0, do nothing andrej@1739: else: # Want to go up ? andrej@1758: res += 1 # Test for n-1 etisserant@14: # Finally set IEC Channel etisserant@14: self.BaseParams.setIEC_Channel(res) etisserant@14: return res etisserant@14: Laurent@967: def GetContextualMenuItems(self): Laurent@967: return None Laurent@967: edouard@2696: def GetView(self, onlyopened=False): edouard@3641: if not self._View and not onlyopened and self.EditorType is not None: Edouard@2524: app_frame = self.GetCTRoot().AppFrame Edouard@2524: self._View = self.EditorType(app_frame.TabsOpened, self, app_frame) Edouard@2524: Edouard@2524: return self._View Edouard@2524: laurent@782: def _OpenView(self, name=None, onlyopened=False): edouard@2696: view = self.GetView(onlyopened) Edouard@2524: Edouard@2524: if view is not None: Edouard@2524: if name is None: Edouard@2524: name = self.CTNFullName() laurent@782: app_frame = self.GetCTRoot().AppFrame edouard@2695: app_frame.EditProjectElement(view, name, onlyopened) Edouard@2524: Edouard@2524: return view laurent@675: laurent@774: def _CloseView(self, view): laurent@774: app_frame = self.GetCTRoot().AppFrame laurent@774: if app_frame is not None: laurent@774: app_frame.DeletePage(view) laurent@774: laurent@675: def OnCloseEditor(self, view): laurent@675: if self._View == view: laurent@675: self._View = None laurent@656: Edouard@718: def OnCTNClose(self): laurent@656: if self._View is not None: laurent@780: self._CloseView(self._View) laurent@774: self._View = None etisserant@14: return True etisserant@14: Edouard@718: def _doRemoveChild(self, CTNInstance): Edouard@718: # Remove all children of child Edouard@718: for SubCTNInstance in CTNInstance.IterChildren(): Edouard@718: CTNInstance._doRemoveChild(SubCTNInstance) etisserant@14: # Call the OnCloseMethod Edouard@718: CTNInstance.OnCTNClose() Edouard@717: # Delete confnode dir Edouard@2004: try: Edouard@2004: shutil.rmtree(CTNInstance.CTNPath()) andrej@2182: except Exception: Edouard@2004: pass Edouard@718: # Remove child of Children Edouard@718: self.Children[CTNInstance.CTNType].remove(CTNInstance) Laurent@1033: if len(self.Children[CTNInstance.CTNType]) == 0: Laurent@1033: self.Children.pop(CTNInstance.CTNType) etisserant@14: # Forget it... (View have to refresh) etisserant@14: Edouard@718: def CTNRemove(self): Edouard@717: # Fetch the confnode andrej@1782: # CTNInstance = self.GetChildByName(CTNName) etisserant@14: # Ask to his parent to remove it Edouard@718: self.CTNParent._doRemoveChild(self) Edouard@718: Edouard@718: def CTNAddChild(self, CTNName, CTNType, IEC_Channel=0): etisserant@14: """ Edouard@717: Create the confnodes that may be added as child to this node self Edouard@718: @param CTNType: string desining the confnode class name (get name from CTNChildrenTypes) Edouard@718: @param CTNName: string for the name of the confnode instance Edouard@718: """ Edouard@720: # reorganize self.CTNChildrenTypes tuples from (name, CTNClass, Help) Edouard@718: # to ( name, (CTNClass, Help)), an make a dict kinsamanka@3750: transpose = list(zip(*self.CTNChildrenTypes)) kinsamanka@3750: CTNChildrenTypes = dict(list(zip(transpose[0], list(zip(transpose[1], transpose[2]))))) Edouard@717: # Check that adding this confnode is allowed etisserant@14: try: Edouard@718: CTNClass, CTNHelp = CTNChildrenTypes[CTNType] etisserant@14: except KeyError: andrej@1765: raise Exception(_("Cannot create child {a1} of type {a2} "). andrej@1767: format(a1=CTNName, a2=CTNType)) andrej@1730: Edouard@718: # if CTNClass is a class factory, call it. (prevent unneeded imports) andrej@1778: if isinstance(CTNClass, types.FunctionType): Edouard@718: CTNClass = CTNClass() andrej@1730: Edouard@717: # Eventualy Initialize child instance list for this class of confnode Edouard@718: ChildrenWithSameClass = self.Children.setdefault(CTNType, list()) etisserant@14: # Check count Edouard@718: if getattr(CTNClass, "CTNMaxCount", None) and len(ChildrenWithSameClass) >= CTNClass.CTNMaxCount: kinsamanka@3785: kinsamanka@3785: msg = _("Max count ({a1}) reached for this confnode of type {a2} ").format( kinsamanka@3785: a1=CTNClass.CTNMaxCount, a2=CTNType) kinsamanka@3785: self.GetCTRoot().logger.write_warning(msg) kinsamanka@3785: kinsamanka@3785: return None andrej@1730: Edouard@717: # create the final class, derived of provided confnode and template Edouard@718: class FinalCTNClass(CTNClass, ConfigTreeNode): etisserant@14: """ Edouard@718: ConfNode class is derivated into FinalCTNClass before being instanciated andrej@1730: This way __init__ is overloaded to ensure ConfigTreeNode.__init__ is called Edouard@718: before CTNClass.__init__, and to do the file related stuff. etisserant@14: """ andrej@1868: def __init__(self, parent): andrej@1868: self.CTNParent = parent Edouard@717: # Keep track of the confnode type name andrej@1868: self.CTNType = CTNType etisserant@106: # remind the help string, for more fancy display andrej@1868: self.CTNHelp = CTNHelp Edouard@717: # Call the base confnode template init - change XSD into class members andrej@1868: ConfigTreeNode.__init__(self) etisserant@29: # check name is unique andrej@1868: NewCTNName = self.FindNewName(CTNName) etisserant@14: # If dir have already be made, and file exist andrej@1868: if os.path.isdir(self.CTNPath(NewCTNName)): # and os.path.isfile(self.ConfNodeXmlFilePath(CTNName)): andrej@1733: # Load the confnode.xml file into parameters members andrej@1868: self.LoadXMLParams(NewCTNName) etisserant@20: # Basic check. Better to fail immediately. andrej@1868: if self.BaseParams.getName() != NewCTNName: andrej@1765: raise Exception( andrej@1765: _("Project tree layout do not match confnode.xml {a1}!={a2} "). andrej@1868: format(a1=NewCTNName, a2=self.BaseParams.getName())) Edouard@718: Edouard@718: # Now, self.CTNPath() should be OK andrej@1730: etisserant@15: # Check that IEC_Channel is not already in use. andrej@1868: self.FindNewIEC_Channel(self.BaseParams.getIEC_Channel()) Edouard@717: # Call the confnode real __init__ Edouard@718: if getattr(CTNClass, "__init__", None): andrej@1868: CTNClass.__init__(self) andrej@1733: # Load and init all the children andrej@1868: self.LoadChildren() andrej@1733: # just loaded, nothing to saved andrej@1868: self.ChangesToSave = False etisserant@14: else: Edouard@717: # If confnode do not have corresponding file/dirs - they will be created on Save andrej@1868: self.CTNMakeDir() etisserant@14: # Find an IEC number andrej@1868: self.FindNewIEC_Channel(IEC_Channel) Edouard@717: # Call the confnode real __init__ Edouard@718: if getattr(CTNClass, "__init__", None): andrej@1868: CTNClass.__init__(self) andrej@1868: self.CTNRequestSave() andrej@1733: # just created, must be saved andrej@1868: self.ChangesToSave = True andrej@1868: andrej@1868: def _getBuildPath(self): andrej@1868: return self.CTNParent._getBuildPath() andrej@1730: etisserant@14: # Create the object out of the resulting class andrej@1868: newConfNodeOpj = FinalCTNClass(self) Edouard@718: # Store it in CTNgedChils Edouard@718: ChildrenWithSameClass.append(newConfNodeOpj) andrej@1730: Edouard@717: return newConfNodeOpj andrej@1730: Edouard@718: def ClearChildren(self): Edouard@718: for child in self.IterChildren(): Edouard@718: child.ClearChildren() Edouard@718: self.Children = {} andrej@1730: andrej@1744: def LoadXMLParams(self, CTNName=None): Edouard@718: methode_name = os.path.join(self.CTNPath(CTNName), "methods.py") etisserant@105: if os.path.isfile(methode_name): kinsamanka@3750: exec(compile(open(methode_name, "rb").read(), methode_name, 'exec')) andrej@1730: Laurent@1332: ConfNodeName = CTNName if CTNName is not None else self.CTNName() andrej@1730: lbessard@17: # Get the base xml tree etisserant@20: if self.MandatoryParams: etisserant@203: try: Edouard@718: basexmlfile = open(self.ConfNodeBaseXmlFilePath(CTNName), 'r') Laurent@1330: self.BaseParams, error = _BaseParamsParser.LoadXMLString(basexmlfile.read()) Laurent@1330: if error is not None: andrej@1581: (fname, lnum, src) = ((ConfNodeName + " BaseParams",) + error) andrej@1744: self.GetCTRoot().logger.write_warning(XSDSchemaErrorMessage.format(a1=fname, a2=lnum, a3=src)) Laurent@1315: self.MandatoryParams = ("BaseParams", self.BaseParams) etisserant@106: basexmlfile.close() andrej@2418: except Exception as exc: kinsamanka@3752: msg = _("Couldn't load confnode base parameters {a1} :\n {a2}").format(a1=ConfNodeName, a2=str(exc)) andrej@1581: self.GetCTRoot().logger.write_error(msg) Edouard@718: self.GetCTRoot().logger.write_error(traceback.format_exc()) andrej@1730: etisserant@14: # Get the xml tree Edouard@718: if self.CTNParams: etisserant@203: try: Edouard@718: xmlfile = open(self.ConfNodeXmlFilePath(CTNName), 'r') Laurent@1330: obj, error = self.Parser.LoadXMLString(xmlfile.read()) Laurent@1330: if error is not None: andrej@1581: (fname, lnum, src) = ((ConfNodeName,) + error) andrej@1744: self.GetCTRoot().logger.write_warning(XSDSchemaErrorMessage.format(a1=fname, a2=lnum, a3=src)) Laurent@1315: name = obj.getLocalTag() Laurent@1315: setattr(self, name, obj) Laurent@1315: self.CTNParams = (name, obj) etisserant@106: xmlfile.close() andrej@2418: except Exception as exc: kinsamanka@3752: msg = _("Couldn't load confnode parameters {a1} :\n {a2}").format(a1=ConfNodeName, a2=str(exc)) andrej@1581: self.GetCTRoot().logger.write_error(msg) Edouard@718: self.GetCTRoot().logger.write_error(traceback.format_exc()) andrej@1730: Edouard@718: def LoadChildren(self): Edouard@718: # Iterate over all CTNName@CTNType in confnode directory, and try to open them Edouard@718: for CTNDir in os.listdir(self.CTNPath()): Edouard@718: if os.path.isdir(os.path.join(self.CTNPath(), CTNDir)) and \ Edouard@718: CTNDir.count(NameTypeSeparator) == 1: Edouard@718: pname, ptype = CTNDir.split(NameTypeSeparator) etisserant@203: try: Edouard@718: self.CTNAddChild(pname, ptype) andrej@2418: except Exception as exc: kinsamanka@3752: msg = _("Could not add child \"{a1}\", type {a2} :\n{a3}\n").format(a1=pname, a2=ptype, a3=str(exc)) andrej@1581: self.GetCTRoot().logger.write_error(msg) Edouard@718: self.GetCTRoot().logger.write_error(traceback.format_exc()) Edouard@2640: Edouard@2640: Edouard@2640: def FatalError(self, message): Edouard@2640: """ Raise an exception that will trigger error message intended to Edouard@2640: the user, but without backtrace since it is not a software error """ Edouard@2640: Edouard@2640: raise UserAddressedException(message) Edouard@2640: