def setup(name): """Setup plugin dependencies by name""" # Check there is plugin manager instance if PluginManager.__instance is None: PluginManager() if PluginManager.__instance is None: return False # Log debug information about the plugin Logger.debug(f'MEG Plugins: Setup dependencies for plugin <{name}>') # Get the plugin by name plugin = PluginManager.get(name) # Check if the plugin was found if plugin is None: return False try: # Setup the plugin dependencies plugin.setup().check_returncode() except Exception as e: # Log that setting up the plugin dependencies failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Could not setup dependencies for plugin <{name}>' ) return False return True
def _install_archive_list(archive, archive_list, force): """Install plugin from archive list""" # Get the plugin cache path and create if needed plugins_path = os.path.join(Config.get('path/plugin_cache'), 'local') os.makedirs(plugins_path, exist_ok=True) # Extract found plugins from archive to install retval = True for plugin_name, plugin_list in archive_list: # Get the plugin path plugin_path = os.path.join(plugins_path, plugin_name) try: # Remove the previous plugin path, if necessary if Config.remove_path(plugin_path): # Log caching plugin Logger.debug(f'MEG Plugins: Locally caching plugin <{plugin_path}>') # Extract each plugin from archive to install archive.extractall(plugin_path, plugin_list) # Install cached plugin retval = PluginManager.install_path(plugin_path, force) and retval else: retval = False except Exception as e: # Log that caching plugin locally failed Logger.warning(f'MEG Plugins: {e}') Logger.warning(f'MEG Plugins: Failed to locally cache plugin <{plugin_path}>') retval = False return retval
def save(path='$(path/config)', overwrite=True): """Save a configuration to JSON file""" # Check there is a configuration instance if Config.__instance is None: Config() if Config.__instance is None: return False try: # Get the expanded path expanded_path = Config.expand(path) Logger.debug(f'MEG Config: Saving configuration <{expanded_path}>') # Check the file exists before overwriting if not overwrite and os.path.exists(expanded_path): raise ConfigException( f'Not overwriting existing file <{expanded_path}>') # Make the path to the containing directory if it does not exist dir_path = os.path.dirname(expanded_path) if not os.path.exists(dir_path): os.makedirs(dir_path, exist_ok=True) # Try to open the configuration file for writing config_file = open(expanded_path, "w") # Remove the blocked keys from being saved config_copy = copy.deepcopy(Config.__instance) for blocked_key in Config.__blocked_keys: config_copy._remove( config_copy, [sk for sk in blocked_key.split('/') if sk]) # Try to convert configuration to JSON file json.dump(config_copy, config_file, indent=2) except Exception as e: # Log that saving the configuration failed Logger.warning(f'MEG Config: {e}') Logger.warning( f'MEG Config: Could not save configuration <{expanded_path}>') return False return True
def enable(name): """Enable plugin by name""" # Check there is plugin manager instance if PluginManager.__instance is None: PluginManager() if PluginManager.__instance is None: return False # Get the plugin by name plugin = PluginManager.get(name) # Check if the plugin was found if plugin is None: return False if not plugin.enabled(): try: # Log debug information about the plugin Logger.debug(f'MEG Plugins: Enabling plugin <{name}>') # Enable the plugin plugin.enable() except Exception as e: # Log that enabling the plugin failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Could not enable plugin <{name}>') return False return True
def install_archive(path, force=False): """Install plugin archive""" # Check there is plugin manager instance if PluginManager.__instance is None: PluginManager() if PluginManager.__instance is None: return False # Check if path does not exist if os.path.exists(path): # Log trying to load plugin information from archive Logger.debug(f'MEG Plugins: Installing plugin(s) from archive <{path}>') try: # Check if zip file plugin archive = zipfile.ZipFile(path) # Get the archive list for the found plugins return PluginManager._install_archive_zip(archive, os.path.splitext(os.path.basename(path))[0], force) except zipfile.BadZipFile: try: # Check if tar file plugin archive = tarfile.open(path) # Get the archive list for the found plugins return PluginManager._install_archive_tar(archive, os.path.splitext(os.path.basename(path))[0], force) except Exception as e: # Log exception Logger.warning(f'MEG Plugins: {e}') except Exception as e: # Log exception Logger.warning(f'MEG Plugins: {e}') # Log that installing the plugin information from archive failed Logger.warning(f'MEG Plugins: Could not install plugin(s) from archive <{path}>') return False
def _update_cache(cache_path, update): """Update plugin cache information""" # Log updating plugin cache information Logger.debug(f'MEG Plugins: Updating plugin cache information') # Obtain the available plugins from the plugin cache path retval = True for (path, dirs, files) in os.walk(cache_path): # For each available plugin load the information for d in dirs: # Ignore the repository index in the cache if d == PluginManager.DEFAULT_BARE_REPO_PATH: continue # Log debug information about the available plugin plugin_path = os.path.join(path, d) Logger.debug(f'MEG Plugins: Found plugin cache information <{plugin_path}>') try: # Get the available plugin path and load plugin information plugin = PluginInformation(plugin_path) # Log plugin information PluginManager._log_plugin(plugin) # Add the plugin information to the cache PluginManager.__instance['plugin_cache'][plugin.name()] = plugin except Exception as e: # Log that loading the plugin cache information failed Logger.warning(f'MEG Plugins: {e}') Logger.warning(f'MEG Plugins: Could not load information for plugin cache <{plugin_path}>') retval = False # Do not actually walk the directory tree, only get directories directly under plugin cache path break # Update the plugin information, if needed if retval and update: retval = PluginManager.update() return retval
def push_view(self, panel): """Push a panel onto the stack being viewed.""" if panel is not None: Logger.debug(f'MEG UI: Adding panel "{panel.get_name()}"') # Hide the current panel current_panel = self.get_current_panel() if current_panel is not None: current_panel.on_hide() # Show the current panel panel.on_show() # Update the title for the panel self.set_title(panel) # Update the status for the panel self.set_status(panel) # Get the window central widget container = self.get_panel_container() if container is not None: # Add the panel to the view stack widgets = panel.get_widgets() widgets.setParent(container) title = panel.get_title() index = container.addTab(widgets, 'Home' if not title else title) # Remove the close button if not closable tabbar = container.tabBar() if not panel.get_is_closable(): tabbar.tabButton(index, QtWidgets.QTabBar.RightSide).deleteLater() tabbar.setTabButton(index, QtWidgets.QTabBar.RightSide, None) # Add the panel icon tabbar.setTabIcon(index, panel.get_icon()) # Add the panel to the panel stack self.get_panels().append(panel) # Set the panel to the view container.setCurrentIndex(index)
def remove_view_by_index(self, index): """Remove a panel from the stack being viewed.""" # Get the panel by index Logger.debug(f'MEG UI: Removing panel by index ({index})') panel = self.get_panel_by_index(index) if panel is not None and panel.get_is_closable(): # Remove the panel self.remove_view(panel)
def install_path(path, force=False): """Install plugin from local path""" # Check there is plugin manager instance if PluginManager.__instance is None: PluginManager() if PluginManager.__instance is not None: # Check if path does not exist if os.path.exists(path) and os.path.isdir(path): # Log trying to load plugin information from path Logger.debug( f'MEG Plugins: Installing plugin from path <{path}>') try: # Get the plugin information for the path if no other version is installed plugin = PluginManager._update(path, force) if plugin is not None: # Log plugin information PluginManager._log_plugin(plugin) # Log trying to load plugin information from path Logger.debug( f'MEG Plugins: Installing plugin <{plugin.name()}>' ) elif not force: # The same version exists and no force so this is installed return True # Get the installed plugin path plugin_path = os.path.join(Config.get('path/plugins'), os.path.basename(path)) # Remove the previous plugin path, if necessary if not Config.remove_path(plugin_path): return False # Copy the path to the plugins directory shutil.copytree(path, plugin_path) # Load (or update) plugin information plugin = Plugin(plugin_path) if plugin is not None: # Log plugin information PluginManager._log_plugin(plugin) # Setup plugin dependencies plugin.setup().check_returncode() # Add the plugin PluginManager.__instance['plugins'][ plugin.name()] = plugin return True except Exception as e: # Log that installing the plugin from the path failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Failed to install plugin from path <{path}>' ) return False
def _log_plugin(plugin): """Log plugin information from plugin""" # Log plugin information if isinstance(plugin, PluginInformation): Logger.debug(f"MEG Plugins: Name: {plugin.name()}") Logger.debug(f"MEG Plugins: Version: {plugin.version()}") Logger.debug(f"MEG Plugins: Author: {plugin.author()} <{plugin.email()}>") Logger.debug(f"MEG Plugins: Brief: {plugin.brief()}")
def update_cache(): """Update cached plugin information""" # Check there is plugin manager instance update = False if PluginManager.__instance is None: PluginManager(False) update = True if PluginManager.__instance is None: return False # Log updating plugin information Logger.debug(f'MEG Plugins: Updating plugin cache') # Clear previous plugin cache information if 'plugin_cache' not in PluginManager.__instance or not isinstance( PluginManager.__instance['plugin_cache'], dict): PluginManager.__instance['plugin_cache'] = {} # Get the plugin cache path and create if needed cache_path = os.path.join(Config.get('path/plugin_cache'), 'remote') os.makedirs(cache_path, exist_ok=True) # Open or clone the plugins repository with the plugin cache path cache = GitManager.open_or_clone( os.path.join(cache_path, PluginManager.DEFAULT_BARE_REPO_PATH), Config.get('plugins/url', PluginManager.DEFAULT_CACHE_URL), bare=True) if cache is None: # Log that loading the plugin cache information failed Logger.warning( f'MEG Plugins: Could not update plugin cache information') return False try: # Log updating plugin cache information Logger.debug(f'MEG Plugins: Fetching plugin cache information') # Fetch and update the plugin cache cache.fetch_all() fetch_head = cache.lookup_reference('FETCH_HEAD') if fetch_head is not None: cache.head.set_target(fetch_head.target) # Checkout the plugin information files cache.checkout_head( directory=cache_path, paths=['*/' + PluginInformation.DEFAULT_PLUGIN_INFO_PATH]) except Exception as e: # Log that loading the plugin cache information failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Could not update plugin cache information') return False # Log updating plugin cache information return PluginManager._update_cache(cache_path, update)
def clone(repo_url, repo_path=None, checkout_branch=None, bare=False, *args, **kwargs): """Clone a remote git repository to a local repository""" # Check there is git repository manager instance if GitManager.__instance is None: GitManager() if GitManager.__instance is not None: # Log cloning repo Logger.debug( f'MEG Git: Cloning git repository <{repo_url}> to <{repo_path}>' ) try: # Get the repository path if not provided if repo_path is None: # Get the root path in the following order: # 1. The configured repositories directory path # 2. The configured user directory path # 3. The current working directory path repo_prefix = Config.get( 'path/repos', Config.get('path/user', os.curdir)) # Append the name of the repository to the path if isinstance(repo_url, str): repo_path = os.path.join(repo_prefix, pathlib.Path(repo_url).stem) elif isinstance(repo_url, pathlib.Path): repo_path = os.path.join(repo_prefix, repo_url.stem) else: raise GitException( f'No local repository path was provided and the path could not be determined from the remote <{repo_url}>' ) # Clone the repository by creating a repository instance return GitRepository(repo_path, repo_url, checkout_branch=checkout_branch, bare=bare, *args, **kwargs) except Exception as e: # Log that cloning the repo failed Logger.warning(f'MEG Git: {e}') Logger.warning( f'MEG Git: Could not clone git repository <{repo_url}> to <{repo_path}>' ) return None
def set_view(self, panel): """Set the panel to be viewed in the stack or push the panel onto the stack being viewed.""" if panel is not None: # Get the window central widget container = self.get_panel_container() if container is not None: # Get the index of the panel index = container.indexOf(panel.get_widgets()) if index >= 0: # Set the new panel container.setCurrentIndex(index) # Do not continue since the panel was found do not push Logger.debug(f'MEG UI: Setting panel "{panel.get_name()}"') return # Push the panel instead because it was not found self.push_view(panel)
def unload(name): """Unload plugin by name""" # Get the plugin by name plugin = PluginManager.get(name) if plugin is not None and plugin.loaded(): # Log debug information about the plugin Logger.debug(f'MEG Plugins: Unloading plugin <{plugin.path()}>') try: # Unload the plugin plugin.unload() except Exception as e: # Log that unloading the plugin failed Logger.warning(f'MEG Plugins: {e}') Logger.warning(f'MEG Plugins: Could not unload plugin <{plugin.path()}>') return False return True
def update(): """Update local plugin information""" # Check there is plugin manager instance if PluginManager.__instance is None: PluginManager(False) if PluginManager.__instance is None: return False # Log updating plugin information Logger.debug( f'MEG Plugins: Updating plugin information {Config.get("path/plugins")}' ) # Unload all plugins PluginManager.unload_all() # Clear previous plugin information if 'plugins' not in PluginManager.__instance or not isinstance( PluginManager.__instance['plugins'], dict): PluginManager.__instance['plugins'] = {} # Obtain the available plugins from the plugins path retval = True for (path, dirs, files) in os.walk(Config.get('path/plugins')): # For each plugin load the information for d in dirs: # Log debug information about the plugin plugin_path = os.path.join(path, d) Logger.debug( f'MEG Plugins: Found plugin information <{plugin_path}>') try: # Get the plugin path and load plugin information plugin = PluginManager._update(plugin_path) if plugin is not None: # Log plugin information PluginManager._log_plugin(plugin) # Add the plugin PluginManager.__instance['plugins'][ plugin.name()] = plugin except Exception as e: # Log that loading the plugin information failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Could not load information for plugin <{plugin_path}>' ) # Do not say there was an error if the file does not exists if not isinstance(e, OSError) or e.errno != errno.ENOENT: retval = False # Do not actually walk the directory tree, only get directories directly under plugins path break return retval
def load(self): """Load the plugin""" if not self.loaded(): # Log debug information about the plugin Logger.debug( f'MEG Plugins: Loading plugin script <{self.script()}>') # Try to load plugin plugin_spec = importlib.util.spec_from_file_location( os.path.basename(self.path()), self.script()) if plugin_spec is not None: plugin_module = importlib.util.module_from_spec(plugin_spec) if plugin_module is not None: # Set plugin module self['spec'] = plugin_spec self['module'] = plugin_module # Add the plugin path to the system path sys.path.append(self.path()) # Execute the plugin module sys.modules[plugin_spec.name] = plugin_module plugin_spec.loader.exec_module(plugin_module)
def uninstall(name): """Uninstall plugin by name""" # Log uninstalling plugin Logger.debug(f'MEG Plugins: Uninstalling plugin <{name}>') # Get the plugin by name plugin = PluginManager.get(name) if plugin is not None: try: # Disable (and unload) the plugin, if needed plugin.disable() # Remove the plugin instance PluginManager.__instance['plugins'].pop(name) # Remove the plugin directory return Config.remove_path(plugin.path()) except Exception as e: # Log uninstalling plugin failed Logger.warning(f'MEG Plugins: {e}') Logger.warning(f'MEG Plugins: Failed to uninstall plugin <{name}>') return False return True
def init(repo_path, bare=False, *args, **kwargs): """Open local git repository""" # Check there is git repository manager instance if GitManager.__instance is None: GitManager() if GitManager.__instance is not None: # Log init repo Logger.debug(f'MEG Git: Initializing git repository <{repo_path}>') try: # Initialize the repository return GitRepository(repo_path, bare=bare, init=True, *args, **kwargs) except Exception as e: # Log that opening the repo failed Logger.warning(f'MEG Git: {e}') Logger.warning( f'MEG Git: Could not open git repository <{repo_path}>') return None
def open(repo_path, checkout_branch=None, bare=False, *args, **kwargs): """Open local git repository""" # Check there is git repository manager instance if GitManager.__instance is None: GitManager() if GitManager.__instance is not None: # Log cloning repo Logger.debug(f'MEG Git: Opening git repository <{repo_path}>') try: # Open the repository return GitRepository(repo_path, checkout_branch=checkout_branch, bare=bare, *args, **kwargs) except Exception as e: # Log that opening the repo failed Logger.warning(f'MEG Git: {e}') Logger.warning( f'MEG Git: Could not open git repository <{repo_path}>') return None
def install(name, force=False): """Install plugin by name""" # Log installing plugin Logger.debug(f'MEG Plugins: Installing plugin <{name}>') # Get the available plugin by name available_plugin = PluginManager.get_available(name) if available_plugin is None: # Log failed to install plugin Logger.warning(f'MEG Plugins: Failed to install plugin <{name}>') return False # Log updating plugin cache information Logger.debug( f'MEG Plugins: Found plugin cache information <{available_plugin.path()}>' ) PluginManager._log_plugin(available_plugin) # Get installed plugin by name, if present plugin = PluginManager.get(name) if plugin is not None: # Check plugin is up to date or forcing installation if available_plugin.compare_versions(plugin) <= 0 and not force: return True # Get the plugins cache path plugins_path = Config.get('path/plugins') # Get the plugin installation path plugin_basename = os.path.basename(available_plugin.path()) plugin_path = os.path.join(plugins_path, plugin_basename) try: # Remove the previous plugin path, if necessary if not Config.remove_path(plugin_path): return False # Open the local plugin cache repository cache_path = os.path.join(Config.get('path/plugin_cache'), 'remote', PluginManager.DEFAULT_BARE_REPO_PATH) cache = GitManager.open(cache_path, bare=True) if cache is None: raise GitException( f'Could not open local plugin cache <{cache_path}>') # Log installing plugin Logger.debug(f'MEG Plugins: Installing plugin <{plugin_path}>') # Install plugin by checking out cache.checkout_head(directory=plugins_path, paths=[plugin_basename + '/*']) # Load (or update) plugin information plugin = PluginManager._update(plugin_path, force) if plugin is not None: # Log plugin information PluginManager._log_plugin(plugin) # Setup plugin dependencies plugin.setup().check_returncode() # Add the plugin PluginManager.__instance['plugins'][plugin.name()] = plugin except Exception as e: # Log that loading the plugin cache information failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Could not load information for plugin <{plugin_path}>' ) return False return True
def load(name): """Load plugin by name""" # Check there is plugin manager instance if PluginManager.__instance is None: PluginManager() if PluginManager.__instance is None: return False # Get the plugin by name plugin = PluginManager.get(name) if plugin is not None and not plugin.loaded(): # Log debug information about the plugin Logger.debug(f'MEG Plugins: Found plugin <{plugin.path()}>') try: # Load the plugin plugin.load() except Exception as e: # Log that loading the plugin failed Logger.warning(f'MEG Plugins: {e}') Logger.warning(f'MEG Plugins: Could not load plugin <{plugin.path()}>') PluginManager.unload(name) return False return True
def remove_view(self, panel): """Remove a panel from the stack being viewed.""" # Check if the panel is closable if panel is not None and panel.get_is_closable(): Logger.debug(f'MEG UI: Removing panel "{panel.get_name()}"') # Close the panel panel.on_hide() panel.on_close() # Remove the panel from the list panels = self.get_panels() if panel in panels: panels.remove(panel) if self._current_panel == panel: self._current_panel = None # Get the window central widget container = self.get_panel_container() if container: # Get the index of this panel index = container.indexOf(panel.get_widgets()) if index >= 0: # Remove the panel from the view stack container.removeTab(index) panel.get_widgets().setParent(None)
def load(path='$(path/config)', clear=True): """Load a configuration from JSON file""" # Check there is a configuration instance if Config.__instance is None: Config() if Config.__instance is None: return False # Clear the old configuration before loading a new one, if wanted if clear: Config.clear() try: # Get the configuration path expanded_path = Config.expand(path) Logger.debug( f'MEG Config: Loading configuration <{expanded_path}>') # Try to open the configuration file for reading config_file = open(expanded_path, "r") # Try to parse the JSON configuration file config = json.load(config_file) # Overwrite or update the configuration Config.__instance.update(config) # Keep the correct path for configuration if 'path' not in Config.__instance or not isinstance( Config.__instance['path'], dict): Config.__instance._set_defaults() Config.__instance._set_required(expanded_path) except Exception as e: # Do not say there was an error if the file exists if isinstance(e, OSError) and e.errno == errno.ENOENT: return True # Log that loading the configuration failed Logger.warning(f'MEG Config: {e}') Logger.warning( f'MEG Config: Could not load configuration <{expanded_path}>') return False return True
def __init__(self, plugin_path, **kwargs): """Plugin information constructor""" # Initialize super class constructor super().__init__(**kwargs) # Get the plugin information file path plugin_info_path = os.path.join( plugin_path, PluginInformation.DEFAULT_PLUGIN_INFO_PATH) # Try to load plugin information plugin_info_file = open(plugin_info_path, 'r') # Log debug information about the plugin Logger.debug( f'MEG Plugins: Loading information for plugin <{plugin_info_path}>' ) # Load plugin information plugin_info = json.load(plugin_info_file) # Validate plugin information if plugin_info is not None: for field in PluginInformation.__fields: if field not in plugin_info: raise PluginException( f'Missing required field "{field}" for plugin information <{plugin_info_path}>' ) if not isinstance(plugin_info[field], PluginInformation.__fields[field]): raise PluginException( f'Invalid required field "{field}" for plugin information <{plugin_info_path}>' ) for field in plugin_info: if field not in PluginInformation.__fields: raise PluginException( f'Unknown field "{field}" for plugin information <{plugin_info_path}>' ) # Set plugin information self.update(plugin_info) self['info'] = plugin_info_path self['path'] = plugin_path
def on_start(self): """On application start""" # Log information about version Logger.info(f'MEG: {App.NAME} Version {App.VERSION}') # Log debug information about home directory Logger.debug(f'MEG: Home <{Config.get("path/home")}>') # Load configuration Config.load() # Log debug information about cache and plugin directories Logger.debug(f'MEG: Cache <{Config.get("path/cache")}>') Logger.debug(f'MEG: Plugins <{Config.get("path/plugins")}>') # Update plugins information PluginManager.update() # Load enabled plugins PluginManager.load_enabled()
def on_stop(self): """On application stopped""" # Unload the plugins PluginManager.unload_all() # Write the exit message Logger.debug(f'MEG: Quit')