def test_copy_constructor(self): filename = os.path.join(self.test_dir, "basic_metadata.xml") md = FreeCAD.Metadata(filename) copy_of_md = FreeCAD.Metadata(md) self.assertEqual(md.Name, copy_of_md.Name) self.assertEqual(md.Description, copy_of_md.Description) self.assertEqual(md.Version, copy_of_md.Version)
def test_xml_constructor(self): try: filename = os.path.join(self.test_dir, "basic_metadata.xml") md = FreeCAD.Metadata(filename) except Exception: self.fail("Metadata construction from XML file failed") with self.assertRaises( FreeCAD.Base.XMLBaseException, msg= "Metadata construction from XML file with bad root node did not raise an exception" ): filename = os.path.join(self.test_dir, "bad_root_node.xml") md = FreeCAD.Metadata(filename) with self.assertRaises( FreeCAD.Base.XMLBaseException, msg= "Metadata construction from invalid XML file did not raise an exception" ): filename = os.path.join(self.test_dir, "bad_xml.xml") md = FreeCAD.Metadata(filename) with self.assertRaises( FreeCAD.Base.XMLBaseException, msg= "Metadata construction from XML file with invalid version did not raise an exception" ): filename = os.path.join(self.test_dir, "bad_version.xml") md = FreeCAD.Metadata(filename)
def test_xml_constructor(self): try: filename = os.path.join(self.test_dir, "basic_metadata.xml") md = FreeCAD.Metadata(filename) except Exception: self.fail("Metadata construction from XML file failed") try: filename = os.path.join(self.test_dir, "bad_root_node.xml") md = FreeCAD.Metadata(filename) except Exception as e: FreeCAD.Console.PrintMessage(str(e) + "\n") pass else: self.fail("Metadata construction from XML file with bad root node did not raise an exception") try: filename = os.path.join(self.test_dir, "bad_xml.xml") md = FreeCAD.Metadata(filename) except Exception as e: FreeCAD.Console.PrintMessage(str(e) + "\n") pass else: self.fail("Metadata construction from invalid XML file did not raise an exception") try: filename = os.path.join(self.test_dir, "bad_version.xml") md = FreeCAD.Metadata(filename) except Exception as e: FreeCAD.Console.PrintMessage(str(e) + "\n") pass else: self.fail("Metadata construction from XML file with bad version did not raise an exception")
def load_metadata_file(self, file: str) -> None: if os.path.exists(file): metadata = FreeCAD.Metadata(file) self.set_metadata(metadata) else: FreeCAD.Console.PrintMessage( "Internal error: {} does not exist".format(file))
def load_metadata_file(self, file: str) -> None: """Read a given metadata file and set it as this object's metadata""" if os.path.exists(file): metadata = FreeCAD.Metadata(file) self.set_metadata(metadata) else: FreeCAD.Console.PrintLog(f"Internal error: {file} does not exist")
def test_content_types(self): filename = os.path.join(self.test_dir, "content_items.xml") md = FreeCAD.Metadata(filename) content = md.Content self.assertTrue("workbench" in content) self.assertTrue("preferencepack" in content) self.assertTrue("macro" in content) self.assertTrue("other_content_item" in content) workbenches = content["workbench"] preferencepacks = content["preferencepack"] macros = content["macro"] other = content["other_content_item"] self.assertEqual(len(workbenches), 3) self.assertEqual(len(macros), 2) self.assertEqual(len(preferencepacks), 1)
def check_package(self, package: Addon) -> None: """Given a packaged Addon package, check it for updates. If git is available that is used. If not, the package's metadata is examined, and if the metadata file has changed compared to the installed copy, an update is flagged.""" clonedir = self.moddir + os.sep + package.name if os.path.exists(clonedir): # First, try to just do a git-based update, which will give the most accurate results: if self.git_manager: self.check_workbench(package) if package.status() != Addon.Status.CANNOT_CHECK: # It worked, just exit now return # If we were unable to do a git-based update, try using the package.xml file instead: installed_metadata_file = os.path.join(clonedir, "package.xml") if not os.path.isfile(installed_metadata_file): # If there is no package.xml file, then it's because the package author added it # after the last time the local installation was updated. By definition, then, # there is an update available, if only to download the new XML file. package.set_status(Addon.Status.UPDATE_AVAILABLE) package.installed_version = None return package.updated_timestamp = os.path.getmtime(installed_metadata_file) try: installed_metadata = FreeCAD.Metadata(installed_metadata_file) package.installed_version = installed_metadata.Version # Packages are considered up-to-date if the metadata version matches. Authors # should update their version string when they want the addon manager to alert # users of a new version. if package.metadata.Version != installed_metadata.Version: package.set_status(Addon.Status.UPDATE_AVAILABLE) else: package.set_status(Addon.Status.NO_UPDATE_AVAILABLE) except Exception: FreeCAD.Console.PrintWarning( translate( "AddonsInstaller", "Failed to read metadata from {name}", ).format(name=installed_metadata_file) + "\n" ) package.set_status(Addon.Status.CANNOT_CHECK)
def resolve_fetch(self): """Called when the data fetch completed, either with an error, or if it found the metadata file""" if self.fetch_task.error( ) == QtNetwork.QNetworkReply.NetworkError.NoError: FreeCAD.Console.PrintLog( f"Found a package.xml file for {self.repo.name}\n") self.repo.repo_type = AddonManagerRepo.RepoType.PACKAGE new_xml = self.fetch_task.readAll() hasher = hashlib.sha1() hasher.update(new_xml.data()) new_sha1 = hasher.hexdigest() self.last_sha1 = new_sha1 # Determine if we need to download the icon: only do that if the # package.xml file changed (since # a change in the version number will show up as a change in the # SHA1, without having to actually # read the metadata) if self.repo.name in self.index: cached_sha1 = self.index[self.repo.name] if cached_sha1 != new_sha1: self.update_local_copy(new_xml) else: # Assume that if the package.xml file didn't change, # neither did the icon, so don't waste # resources downloading it xml_file = os.path.join(self.store, self.repo.name, "package.xml") self.repo.metadata = FreeCAD.Metadata(xml_file) else: # There is no local copy yet, so we definitely have to update # the cache self.update_local_copy(new_xml) elif (self.fetch_task.error() == QtNetwork.QNetworkReply.NetworkError.ContentNotFoundError): pass elif (self.fetch_task.error() == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError): pass else: FreeCAD.Console.PrintWarning( translate("AddonsInstaller", "Failed to connect to URL") + f":\n{self.url}\n {self.fetch_task.error()}\n")
def update_local_copy(self, new_xml): # We have to update the local copy of the metadata file and re-download # the icon file name = self.repo.name repo_url = self.repo.url package_cache_directory = os.path.join(self.store, name) if not os.path.exists(package_cache_directory): os.makedirs(package_cache_directory) new_xml_file = os.path.join(package_cache_directory, "package.xml") with open(new_xml_file, "wb") as f: f.write(new_xml.data()) metadata = FreeCAD.Metadata(new_xml_file) self.repo.metadata = metadata self.repo.repo_type = AddonManagerRepo.RepoType.PACKAGE icon = metadata.Icon if not icon: # If there is no icon set for the entire package, see if there are # any workbenches, which are required to have icons, and grab the first # one we find: content = self.repo.metadata.Content if "workbench" in content: wb = content["workbench"][0] if wb.Icon: if wb.Subdirectory: subdir = wb.Subdirectory else: subdir = wb.Name self.repo.Icon = subdir + wb.Icon icon = self.repo.Icon icon_url = utils.construct_git_url(self.repo, icon) icon_stream = utils.urlopen(icon_url) if icon and icon_stream and icon_url: icon_data = icon_stream.read() cache_file = self.repo.get_cached_icon_filename() with open(cache_file, "wb") as icon_file: icon_file.write(icon_data) self.repo.cached_icon_filename = cache_file self.updated.emit(self.repo)
def process_package_xml(self, repo: Addon, data: QtCore.QByteArray): """Process the package.xml metadata file""" repo.repo_type = Addon.Kind.PACKAGE # By definition package_cache_directory = os.path.join(self.store, repo.name) if not os.path.exists(package_cache_directory): os.makedirs(package_cache_directory) new_xml_file = os.path.join(package_cache_directory, "package.xml") with open(new_xml_file, "wb") as f: f.write(data.data()) metadata = FreeCAD.Metadata(new_xml_file) repo.metadata = metadata self.status_message.emit( translate("AddonsInstaller", "Downloaded package.xml for {}").format( repo.name ) ) # Grab a new copy of the icon as well: we couldn't enqueue this earlier because # we didn't know the path to it, which is stored in the package.xml file. icon = metadata.Icon if not icon: # If there is no icon set for the entire package, see if there are # any workbenches, which are required to have icons, and grab the first # one we find: content = repo.metadata.Content if "workbench" in content: wb = content["workbench"][0] if wb.Icon: if wb.Subdirectory: subdir = wb.Subdirectory else: subdir = wb.Name repo.Icon = subdir + wb.Icon icon = repo.Icon icon_url = utils.construct_git_url(repo, icon) index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(icon_url) self.requests[index] = (repo, UpdateMetadataCacheWorker.RequestType.ICON) self.total_requests += 1
def test_toplevel_tags(self): filename = os.path.join(self.test_dir, "basic_metadata.xml") md = FreeCAD.Metadata(filename) # Tags with only one element: self.assertEqual(md.Name, "Test Workbench") self.assertEqual(md.Description, "A package.xml file for unit testing.") self.assertEqual(md.Version, "1.0.1") #self.assertEqual(md.Date, "2022-01-07") self.assertEqual(md.Icon, "Resources/icons/PackageIcon.svg") # Tags that are lists of elements: maintainers = md.Maintainer self.assertEqual(len(maintainers), 2) authors = md.Author self.assertEqual(len(authors), 3) urls = md.Urls self.assertEqual(len(urls), 4) tags = md.Tag self.assertEqual(len(tags), 2)
def InitApplications(): # Checking on FreeCAD module path ++++++++++++++++++++++++++++++++++++++++++ ModDir = FreeCAD.getHomePath()+'Mod' ModDir = os.path.realpath(ModDir) ExtDir = FreeCAD.getHomePath()+'Ext' ExtDir = os.path.realpath(ExtDir) BinDir = FreeCAD.getHomePath()+'bin' BinDir = os.path.realpath(BinDir) libpaths = [] LibDir = FreeCAD.getHomePath()+'lib' LibDir = os.path.realpath(LibDir) if os.path.exists(LibDir): libpaths.append(LibDir) Lib64Dir = FreeCAD.getHomePath()+'lib64' Lib64Dir = os.path.realpath(Lib64Dir) if os.path.exists(Lib64Dir): libpaths.append(Lib64Dir) LibPyDir = FreeCAD.getHomePath()+'lib-py3' LibPyDir = os.path.realpath(LibPyDir) if (os.path.exists(LibPyDir)): libpaths.append(LibPyDir) AddPath = FreeCAD.ConfigGet("AdditionalModulePaths").split(";") HomeMod = FreeCAD.getUserAppDataDir()+"Mod" HomeMod = os.path.realpath(HomeMod) MacroDir = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString("MacroPath") MacroMod = os.path.realpath(MacroDir+"/Mod") SystemWideMacroDir = FreeCAD.getHomePath()+'Macro' SystemWideMacroDir = os.path.realpath(SystemWideMacroDir) #print FreeCAD.getHomePath() if os.path.isdir(FreeCAD.getHomePath()+'src\\Tools'): sys.path.append(FreeCAD.getHomePath()+'src\\Tools') # Searching for module dirs +++++++++++++++++++++++++++++++++++++++++++++++++++ # Use dict to handle duplicated module names ModDict = {} if os.path.isdir(ModDir): ModDirs = os.listdir(ModDir) for i in ModDirs: ModDict[i.lower()] = os.path.join(ModDir,i) else: Wrn ("No modules found in " + ModDir + "\n") # Search for additional modules in the home directory if os.path.isdir(HomeMod): HomeMods = os.listdir(HomeMod) for i in HomeMods: ModDict[i.lower()] = os.path.join(HomeMod,i) elif os.path.isdir(os.path.join(os.path.expanduser("~"),".FreeCAD","Mod")): # Check if old location exists Wrn ("User path has changed to " + FreeCAD.getUserAppDataDir() + ". Please move user modules and macros\n") # Search for additional modules in the macro directory if os.path.isdir(MacroMod): MacroMods = os.listdir(MacroMod) for i in MacroMods: key = i.lower() if key not in ModDict: ModDict[key] = os.path.join(MacroMod,i) # Search for additional modules in command line for i in AddPath: if os.path.isdir(i): ModDict[i] = i #AddModPaths = App.ParamGet("System parameter:AdditionalModulePaths") #Err( AddModPaths) # add also this path so that all modules search for libraries # they depend on first here PathExtension = [] PathExtension.append(BinDir) # prepend all module paths to Python search path Log('Init: Searching for modules...\n') # to have all the module-paths available in FreeCADGuiInit.py: FreeCAD.__ModDirs__ = list(ModDict.values()) # this allows importing with: # from FreeCAD.Module import package FreeCAD.__path__ = [ModDir] + libpaths + [HomeMod] # also add these directories to the sys.path to # not change the old behaviour. once we have moved to # proper python modules this can eventually be removed. sys.path = [ModDir] + libpaths + [ExtDir] + sys.path # The AddonManager may install additional Python packages in # this path: additional_packages_path = os.path.join(FreeCAD.getUserAppDataDir(),"AdditionalPythonPackages") if os.path.isdir(additional_packages_path): sys.path.append(additional_packages_path) def RunInitPy(Dir): InstallFile = os.path.join(Dir,"Init.py") if (os.path.exists(InstallFile)): try: with open(file=InstallFile, encoding="utf-8") as f: exec(f.read()) except Exception as inst: Log('Init: Initializing ' + Dir + '... failed\n') Log('-'*100+'\n') Log(traceback.format_exc()) Log('-'*100+'\n') Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') Err('Please look into the log file for further information\n') else: Log('Init: Initializing ' + Dir + '... done\n') else: Log('Init: Initializing ' + Dir + '(Init.py not found)... ignore\n') for Dir in ModDict.values(): if ((Dir != '') & (Dir != 'CVS') & (Dir != '__init__.py')): sys.path.insert(0,Dir) PathExtension.append(Dir) MetadataFile = os.path.join(Dir, "package.xml") if os.path.exists(MetadataFile): meta = FreeCAD.Metadata(MetadataFile) content = meta.Content if "workbench" in content: workbenches = content["workbench"] for workbench in workbenches: subdirectory = workbench.Name if not workbench.Subdirectory else workbench.Subdirectory subdirectory = subdirectory.replace("/",os.path.sep) subdirectory = os.path.join(Dir, subdirectory) classname = workbench.Classname sys.path.insert(0,subdirectory) PathExtension.append(subdirectory) RunInitPy(subdirectory) else: continue # The package content says there are no workbenches here, so just skip else: RunInitPy(Dir) extension_modules = [] try: import pkgutil import importlib import freecad for _, freecad_module_name, freecad_module_ispkg in pkgutil.iter_modules(freecad.__path__, "freecad."): if freecad_module_ispkg: Log('Init: Initializing ' + freecad_module_name + '\n') try: freecad_module = importlib.import_module(freecad_module_name) extension_modules += [freecad_module_name] if any (module_name == 'init' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)): importlib.import_module(freecad_module_name + '.init') Log('Init: Initializing ' + freecad_module_name + '... done\n') else: Log('Init: No init module found in ' + freecad_module_name + ', skipping\n') except Exception as inst: Err('During initialization the error "' + str(inst) + '" occurred in ' + freecad_module_name + '\n') Err('-'*80+'\n') Err(traceback.format_exc()) Err('-'*80+'\n') Log('Init: Initializing ' + freecad_module_name + '... failed\n') Log('-'*80+'\n') Log(traceback.format_exc()) Log('-'*80+'\n') except ImportError as inst: Err('During initialization the error "' + str(inst) + '" occurred\n') Log("Using "+ModDir+" as module path!\n") # In certain cases the PathExtension list can contain invalid strings. We concatenate them to a single string # but check that the output is a valid string setupSearchPaths(PathExtension) path = os.environ["PATH"].split(os.pathsep) Log("System path after init:\n") for i in path: Log(" " + i + "\n") # add MacroDir to path (RFE #0000504) sys.path.append(MacroDir) # add SystemWideMacroDir to path sys.path.append(SystemWideMacroDir) # add special path for MacOSX (bug #0000307) import platform if len(platform.mac_ver()[0]) > 0: sys.path.append(os.path.expanduser('~/Library/Application Support/FreeCAD/Mod'))
def InitApplications(): import sys, os, traceback try: # Python3 import io as cStringIO except ImportError: # Python2 import cStringIO # Searching modules dirs +++++++++++++++++++++++++++++++++++++++++++++++++++ # (additional module paths are already cached) ModDirs = FreeCAD.__ModDirs__ #print ModDirs Log('Init: Searching modules...\n') def RunInitGuiPy(Dir) -> bool: InstallFile = os.path.join(Dir, "InitGui.py") if (os.path.exists(InstallFile)): try: with open(file=InstallFile, encoding="utf-8") as f: exec(f.read()) except Exception as inst: Log('Init: Initializing ' + Dir + '... failed\n') Log('-' * 100 + '\n') Log(traceback.format_exc()) Log('-' * 100 + '\n') Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') Err('Please look into the log file for further information\n') else: Log('Init: Initializing ' + Dir + '... done\n') return True else: Log('Init: Initializing ' + Dir + '(InitGui.py not found)... ignore\n') return False for Dir in ModDirs: if ((Dir != '') & (Dir != 'CVS') & (Dir != '__init__.py')): stopFile = os.path.join(Dir, "ADDON_DISABLED") if os.path.exists(stopFile): Msg(f'NOTICE: Addon "{Dir}" disabled by presence of ADDON_DISABLED stopfile\n' ) continue MetadataFile = os.path.join(Dir, "package.xml") if os.path.exists(MetadataFile): meta = FreeCAD.Metadata(MetadataFile) if not meta.supportsCurrentFreeCAD(): continue content = meta.Content if "workbench" in content: FreeCAD.Gui.addIconPath(Dir) workbenches = content["workbench"] for workbench_metadata in workbenches: if not workbench_metadata.supportsCurrentFreeCAD(): continue subdirectory = workbench_metadata.Name if not workbench_metadata.Subdirectory else workbench_metadata.Subdirectory subdirectory = subdirectory.replace("/", os.path.sep) subdirectory = os.path.join(Dir, subdirectory) ran_init = RunInitGuiPy(subdirectory) if ran_init: # Try to generate a new icon from the metadata-specified information classname = workbench_metadata.Classname if classname: try: wb_handle = FreeCAD.Gui.getWorkbench( classname) except Exception: Log(f"Failed to get handle to {classname} -- no icon can be generated, check classname in package.xml\n" ) else: GeneratePackageIcon( dir, subdirectory, workbench_metadata, wb_handle) else: continue # The package content says there are no workbenches here, so just skip else: RunInitGuiPy(Dir) Log("All modules with GUIs using InitGui.py are now initialized\n") extension_modules = [] try: import pkgutil import importlib import freecad freecad.gui = FreeCADGui for _, freecad_module_name, freecad_module_ispkg in pkgutil.iter_modules( freecad.__path__, "freecad."): # Check for a stopfile stopFile = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", freecad_module_name[8:], "ADDON_DISABLED") if os.path.exists(stopFile): continue # Make sure that package.xml (if present) does not exclude this version of FreeCAD MetadataFile = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", freecad_module_name[8:], "package.xml") if os.path.exists(MetadataFile): meta = FreeCAD.Metadata(MetadataFile) if not meta.supportsCurrentFreeCAD(): continue if freecad_module_ispkg: Log('Init: Initializing ' + freecad_module_name + '\n') try: freecad_module = importlib.import_module( freecad_module_name) if any(module_name == 'init_gui' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)): importlib.import_module(freecad_module_name + '.init_gui') Log('Init: Initializing ' + freecad_module_name + '... done\n') else: Log('Init: No init_gui module found in ' + freecad_module_name + ', skipping\n') except Exception as inst: Err('During initialization the error "' + str(inst) + '" occurred in ' + freecad_module_name + '\n') Err('-' * 80 + '\n') Err(traceback.format_exc()) Err('-' * 80 + '\n') Log('Init: Initializing ' + freecad_module_name + '... failed\n') Log('-' * 80 + '\n') Log(traceback.format_exc()) Log('-' * 80 + '\n') except ImportError as inst: Err('During initialization the error "' + str(inst) + '" occurred\n') Log("All modules with GUIs initialized using pkgutil are now initialized\n" )
def load_metadata_file(self, file: str) -> None: if os.path.isfile(file): metadata = FreeCAD.Metadata(file) self.set_metadata(metadata)
def test_default_constructor(self): try: _ = FreeCAD.Metadata() except Exception: self.fail("Metadata default constructor failed")