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 _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 _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 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 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 _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 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 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, 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 __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_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 _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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 push(self, remote_name='origin', username=None, password=None): """Pushes current commits 4/13/20 21 - seems to be working Args: remote_name (string, optional): name of the remote to push to username (string, optional): username of user account used for pushing password (string, optional): password of user account used for pushing """ if username is None: username = Config.get('user/username') if password is None: password = Config.get('user/password') creds = pygit2.UserPass(username, password) remote = self.remotes[remote_name] remote.credentials = creds try: remote.push([self.head.name], callbacks=pygit2.RemoteCallbacks(credentials=creds)) except GitError as e: Logger.warning(e) Logger.warning("MEG Git Repository: Failed to push commit")