Exemplo n.º 1
0
 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')
Exemplo n.º 3
0
 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']
Exemplo n.º 4
0
 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
Exemplo n.º 5
0
 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)
Exemplo n.º 6
0
 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()
Exemplo n.º 7
0
 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)
Exemplo n.º 8
0
 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 +
             '>')
Exemplo n.º 9
0
 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']
Exemplo n.º 10
0
 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))
Exemplo n.º 11
0
 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
Exemplo n.º 12
0
 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
Exemplo n.º 13
0
 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}"')
Exemplo n.º 14
0
 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()}")
Exemplo n.º 15
0
 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)
Exemplo n.º 16
0
    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
Exemplo n.º 17
0
 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
Exemplo n.º 18
0
 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)
Exemplo n.º 19
0
 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
Exemplo n.º 20
0
 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}"')
Exemplo n.º 21
0
 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 ''
Exemplo n.º 22
0
    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
Exemplo n.º 23
0
 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)
Exemplo n.º 24
0
 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')
Exemplo n.º 25
0
 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
Exemplo n.º 26
0
 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
Exemplo n.º 27
0
 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
Exemplo n.º 28
0
 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
Exemplo n.º 29
0
 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
Exemplo n.º 30
0
 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