def _update(plugin_path, force=False): """Create plugin information from plugin path""" # Get the plugin path and load plugin information plugin = Plugin(plugin_path) # If forcing do not bother checking new version if not force: # Check the plugin does not already exist by name current_plugin = PluginManager.get(plugin.name()) if current_plugin is not None: # Determine higher version and keep that plugin comparison = Plugin.compare_versions(plugin, current_plugin) # The versions were equal so check if there are additional version fields if comparison == 0: # This plugin appears to be the same version so skip this one Logger.info( f'Plugin "{plugin.name()}" with version {plugin.version()} ignored because the same version already exists' ) plugin = None elif comparison < 0: # This plugin is older version than the previously loaded plugin so skip this one raise PluginException( f'Plugin "{plugin.name()}" with version {plugin.version()} ignored because newer version {current_plugin.version()} already exists' ) else: # This plugin is newer version than the previously loaded plugin so replace with this one Logger.info( f'MEG Plugins: Plugin "{plugin.name()}" with version {current_plugin.version()} replaced with newer version {plugin.version()}' ) # Return the created plugin information return plugin
def load(self): """Load the repository permission file""" self.update({ "roles": { "default": [], "admin": [] }, "files": {}, "general": { "users_remove_locks": [], "roles_remove_locks": ["default", "admin"], "users_add_locks": [], "roles_add_locks": ["default", "admin"], "users_write": [], "roles_write": ["default", "admin"], "users_grant": [], "roles_grant": ["default", "admin"], "users_modify_roles": [], "roles_modify_roles": ["default", "admin"] } }) try: self.update(json.load(open(self.__path))) except FileNotFoundError: # Log that loading the configuration failed Logger.info('MEG PERMISSIONS: Could not load permissions file <' + self.__path + '>, using default permissions')
def _set_required(self, config_path): """Set the default values""" # Get the user path from the environment, if present, otherwise use the default path self['path']['user'] = str( Path.home() ) if 'MEG_USER_PATH' not in os.environ else os.environ['MEG_USER_PATH'] # Load configuration from the environment, if present, otherwise use the default path self['path']['config'] = config_path # Get the default downloads path, if not already present if 'downloads' not in self['path']: # Get the default downloads path downloads_path = os.path.join('$(path/user)', 'Downloads') if os.name == 'nt': try: import winreg # For windows, the registry must be queried for the correct default downloads path with winreg.OpenKey( winreg.HKEY_CURRENT_USER, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders' ) as key: downloads_path = winreg.QueryValueEx( key, '{374DE290-123F-4565-9164-39C4925E467B}')[0] except Exception as e: Logger.warning(f'MEG Config: {e}') # Get the downloads directory from the environment, if present, otherwise use the default path self['path'][ 'downloads'] = downloads_path if 'MEG_DOWNLOADS_PATH' not in os.environ else os.environ[ 'MEG_DOWNLOADS_PATH']
def load(self, filepath=None): """Loads this object with the current data in the lockfile, overrideing its current data If the file doesn't exist, create one Args: filepath (string): path to the lockfile Returns: (bool): False if lockfile cannot be read """ self._lockData = {} if(filepath is None): filepath = self._filepath else: self._filepath = filepath if(not os.path.exists(filepath)): self._createLockFile(filepath) try: self._lockData = json.load(open(filepath))["locks"] except (json.decoder.JSONDecodeError, KeyError): self._createLockFile(filepath) try: self._lockData = json.load(open(filepath))["locks"] except (json.decoder.JSONDecodeError, KeyError): Logger.warning("MEG Locking: Unable to read contents of lock file at {0}".format(self._filepath)) return False return True
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 _generateLockFile(self): """If the lock file doesn't exist, generate the directory tree and create the file """ if not os.path.isfile(self.__path): Logger.info("MEG LOCKING: GENERATING LOCK FILE") os.makedirs(os.path.dirname(self.__path), exist_ok=True) open(self.__path, 'w+').close()
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 __init__(self, path, user): """Load the repository permission file""" self._user = user try: self.update(json.load(open(path))) except Exception as e: # Log that loading the configuration failed Logger.warning('MEG Permission: {0}'.format(e)) Logger.warning( 'MEG Permission: Could not load permissions file <' + path + '>')
def can_write(self, path): """Return True if the current user can write to a specific path""" if 'files' not in self or path not in self['files']: Logger.warning('MEG Permission: Path <' + path + '> does not exist in the permissions file.') return False roles = self._get_roles() for role in roles: if role in self['files'][path]['roles_write']: return True return self._user in self['files'][path]['users_write']
def __init__(self, **kwargs): """UI manager constructor.""" super().__init__(**kwargs) # Load the UI file path = pkg_resources.resource_filename( __name__, f'/{self.__class__.__name__.lower()}.ui' ) try: uic.loadUi(path, self) except Exception as e: Logger.warning('MEG: BasePanel: {}'.format(e)) Logger.warning('MEG: BasePanel: Could not load path {}'.format(path))
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 _handle_double_clicked(self, item): """Handle double clicking of a file (open it with another program).""" path = self.tree_view.get_selected_path() if not os.path.isdir(path): try: if platform.system() == 'Darwin': subprocess.run(['open', path]) elif platform.system() == 'Windows': os.startfile(path) else: subprocess.run(['xdg-open', path]) except Exception as e: Logger.warning(f'MEG RepoPanel: {e}') Logger.warning(f'MEG RepoPanel: Could not open the file {path}') QtWidgets.QMessageBox.warning(App.get_window(), App.get_name(), f'Could not open file "{path}"')
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 loads(self, s): """Loads this object with the current data in the lockfile Discards the any currently held locks Args: s (string | bytes | bytearray): a json containing string Returns: (bool): False if json was malformed """ self.__lockData = {} try: self.__lockData = json.loads(s) except json.decoder.JSONDecodeError: Logger.warning("MEG Locking: Blob json is malformed") return False return True
def loads(self, s): """Loads this object with the current data in the lockfile, overrideing its current data If the file doesn't exist, create one Args: s (string | bytes | bytearray): a json containing string Returns: (bool): False if json was malformed """ self._lockData = {} self._filepath = None try: self._lockData = json.loads(s) except (json.decoder.JSONDecodeError, KeyError): Logger.warning("MEG Locking: Blob json is malformed") return False return True
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 replace(str): """Replace a configuration from a JSON string""" # Check there is a configuration instance if Config.__instance is None: Config() if Config.__instance is None: return False try: values = json.loads(str) super(Config, Config.__instance).clear() Config.__instance.update(values) Config.__instance._set_defaults() return True except Exception as e: # Log that updating the configuration failed Logger.warning(f'MEG Config: {e}') return False
def _handle_double_click(self, item): """Handle a double click.""" repo_path = None try: repo_path = item.text(1) repo_url = item.text(2) # Open or clone the repo repo = GitManager.open_or_clone(repo_path, repo_url) # Create the repository panel App.get_window().push_view(RepoPanel(repo)) except Exception as e: Logger.warning(f'MEG UIManager: {e}') Logger.warning( f'MEG UIManager: Could not load repository "{repo_path}"') # Popup QtWidgets.QMessageBox.warning( App.get_window(), App.get_name(), f'Could not load repository "{repo_path}"')
def dump(): """Dump a configuration to JSON string""" # Check there is a configuration instance if Config.__instance is None: Config() if Config.__instance is None: return False try: # 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 return json.dumps(config_copy, indent=2) except Exception as e: # Log that dumping the configuration failed Logger.warning(f'MEG Config: {e}') return ''
def pullPaths(self, paths): """Checkout only the files in the list of paths Args: paths (list(stirng)): paths to checkout Returns: (bool): Were the paths sucessfuly checkedout """ self.fetch_all() fetch_head = self.lookup_reference('FETCH_HEAD') if fetch_head is not None: try: self.head.set_target(fetch_head.target) self.checkout_head(paths=paths) return True except GitError as e: Logger.warning(f'MEG Repositiory: {e}') Logger.warning(f'MEG Repositiory: Could not checkout paths') return False
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 on_load(self): """Load dynamic elements within the panel.""" instance = self.get_widgets() self._get_changes_button = instance.findChild(QtWidgets.QPushButton, 'getChanges') self._get_changes_button.clicked.connect(self.get_changes) self._send_changes_button = instance.findChild(QtWidgets.QPushButton, 'sendChanges') self._send_changes_button.clicked.connect(self.send_changes) self._manage_roles_button = instance.findChild(QtWidgets.QPushButton, 'manageRoles') self._manage_roles_button.clicked.connect(self.manage_roles) self._branch_name_label = instance.findChild(QtWidgets.QLabel, 'branchName') # Setup the tree view of the repo if the repo folder exists if os.path.exists(self._repo.path): path = self._repo.path self.tree_view = FileChooser(instance.findChild(QtWidgets.QTreeView, 'treeView'), path) header = self.tree_view._tree_view.header() header.resizeSection(0, header.sectionSize(0) * 3) # Setup a double click function if necessary self.tree_view.set_double_click_handler(self._handle_double_clicked) else: Logger.warning(f'MEG RepoPanel: The path "{self._repo.path}" for this repo does not exist')
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 download(url, path=None, unique_path=True, force=True): """Attempt to download to a local path from a remote url""" # Check the configuration dictionary is valid if Config.__instance is None: Config() if not isinstance(url, str) or Config.__instance is None: return None # Get the download path from the given path or url download_path = Config.unique_path( path if path else os.path.join(Config.get('path/downloads'), os.path.basename(url)), unique_path, force) if download_path: # Log downloading Logger.warning( f'MEG Config: Downloading url <{url}> to <{download_path}>') try: # Download remote request content req = requests.get(url, allow_redirects=True) # Save remote request to download path open(download_path, "wb").write(req.content) except Exception as e: # Log that downloading failed Logger.warning(f'MEG Config: {e}') Logger.warning( f'MEG Config: Could not download url <{url}> to <{download_path}>' ) return None # Return the download path on success return download_path
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 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 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_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