示例#1
0
    def f(name, implementation):
        """Factory for injections for all OctoPrintPlugins"""
        if not isinstance(implementation, octoprint.plugin.OctoPrintPlugin):
            return None

        components_copy = dict(components)
        if "printer" in components:
            import functools

            import wrapt

            def tagwrap(f):
                @functools.wraps(f)
                def wrapper(*args, **kwargs):
                    tags = kwargs.get("tags", set()) | {
                        "source:plugin",
                        "plugin:{}".format(name),
                    }
                    kwargs["tags"] = tags
                    return f(*args, **kwargs)

                wrapper.__tagwrapped__ = True
                return wrapper

            class TaggedFuncsPrinter(wrapt.ObjectProxy):
                def __getattribute__(self, attr):
                    __wrapped__ = super(TaggedFuncsPrinter,
                                        self).__getattribute__("__wrapped__")
                    if attr == "__wrapped__":
                        return __wrapped__

                    item = getattr(__wrapped__, attr)
                    if (callable(item)
                            and ("tags" in item.__code__.co_varnames
                                 or "kwargs" in item.__code__.co_varnames)
                            and not getattr(item, "__tagwrapped__", False)):
                        return tagwrap(item)
                    else:
                        return item

            components_copy["printer"] = TaggedFuncsPrinter(
                components["printer"])

        props = {}
        props.update(components_copy)
        props.update({
            "data_folder":
            os.path.join(settings.getBaseFolder("data"), name)
        })
        return props
示例#2
0
def get_plugin_blacklist(settings, connectivity_checker=None):
	import requests
	import os
	import time
	import yaml

	from octoprint.util import bom_aware_open
	from octoprint.util.version import is_octoprint_compatible

	logger = log.getLogger(__name__ + ".startup")

	if connectivity_checker is not None and not connectivity_checker.online:
		logger.info("We don't appear to be online, not fetching plugin blacklist")
		return []

	def format_blacklist(entries):
		format_entry = lambda x: "{} ({})".format(x[0], x[1]) if isinstance(x, (list, tuple)) and len(x) == 2 \
			else "{} (any)".format(x)
		return ", ".join(map(format_entry, entries))

	def process_blacklist(entries):
		result = []

		if not isinstance(entries, list):
			return result

		for entry in entries:
			if not "plugin" in entry:
				continue

			if "octoversions" in entry and not is_octoprint_compatible(*entry["octoversions"]):
				continue

			if "version" in entry:
				logger.debug("Blacklisted plugin: {}, version: {}".format(entry["plugin"], entry["version"]))
				result.append((entry["plugin"], entry["version"]))
			elif "versions" in entry:
				logger.debug("Blacklisted plugin: {}, versions: {}".format(entry["plugin"], ", ".join(entry["versions"])))
				for version in entry["versions"]:
					result.append((entry["plugin"], version))
			else:
				logger.debug("Blacklisted plugin: {}".format(entry["plugin"]))
				result.append(entry["plugin"])

		return result

	def fetch_blacklist_from_cache(path, ttl):
		if not os.path.isfile(path):
			return None

		if os.stat(path).st_mtime + ttl < time.time():
			return None

		with bom_aware_open(path, encoding="utf-8", mode="r") as f:
			result = yaml.safe_load(f)

		if isinstance(result, list):
			return result

	def fetch_blacklist_from_url(url, timeout=3, cache=None):
		result = []
		try:
			r = requests.get(url, timeout=timeout)
			result = process_blacklist(r.json())

			if cache is not None:
				try:
					with bom_aware_open(cache, encoding="utf-8", mode="w") as f:
						yaml.safe_dump(result, f)
				except:
					logger.info("Fetched plugin blacklist but couldn't write it to its cache file.")
		except:
			logger.info("Unable to fetch plugin blacklist from {}, proceeding without it.".format(url))
		return result

	try:
		# first attempt to fetch from cache
		cache_path = os.path.join(settings.getBaseFolder("data"), "plugin_blacklist.yaml")
		ttl = settings.getInt(["server", "pluginBlacklist", "ttl"])
		blacklist = fetch_blacklist_from_cache(cache_path, ttl)

		if blacklist is None:
			# no result from the cache, let's fetch it fresh
			url = settings.get(["server", "pluginBlacklist", "url"])
			timeout = settings.getFloat(["server", "pluginBlacklist", "timeout"])
			blacklist = fetch_blacklist_from_url(url, timeout=timeout, cache=cache_path)

		if blacklist is None:
			# still now result, so no blacklist
			blacklist = []

		if blacklist:
			logger.info("Blacklist processing done, "
			            "adding {} blacklisted plugin versions: {}".format(len(blacklist),
			                                                               format_blacklist(blacklist)))
		else:
			logger.info("Blacklist processing done")

		return blacklist
	except:
		logger.exception("Something went wrong while processing the plugin blacklist. Proceeding without it.")
示例#3
0
def init_pluginsystem(settings, safe_mode=False, ignore_blacklist=True, connectivity_checker=None):
	"""Initializes the plugin manager based on the settings."""

	import os

	logger = log.getLogger(__name__ + ".startup")

	plugin_folders = [(os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "plugins")), True),
	                  settings.getBaseFolder("plugins")]
	plugin_entry_points = ["octoprint.plugin"]
	plugin_disabled_list = settings.get(["plugins", "_disabled"])

	plugin_blacklist = []
	if not ignore_blacklist and settings.getBoolean(["server", "pluginBlacklist", "enabled"]):
		plugin_blacklist = get_plugin_blacklist(settings, connectivity_checker=connectivity_checker)

	plugin_validators = []
	if safe_mode:
		def validator(phase, plugin_info):
			if phase in ("before_import", "before_load", "before_enable"):
				setattr(plugin_info, "safe_mode_victim", not plugin_info.bundled)
				if not plugin_info.bundled:
					return False
			return True
		plugin_validators.append(validator)

	from octoprint.plugin import plugin_manager
	pm = plugin_manager(init=True,
	                    plugin_folders=plugin_folders,
	                    plugin_entry_points=plugin_entry_points,
	                    plugin_disabled_list=plugin_disabled_list,
	                    plugin_blacklist=plugin_blacklist,
	                    plugin_validators=plugin_validators)

	settings_overlays = dict()
	disabled_from_overlays = dict()

	def handle_plugin_loaded(name, plugin):
		if plugin.instance and hasattr(plugin.instance, "__plugin_settings_overlay__"):
			plugin.needs_restart = True

			# plugin has a settings overlay, inject it
			overlay_definition = getattr(plugin.instance, "__plugin_settings_overlay__")
			if isinstance(overlay_definition, (tuple, list)):
				overlay_definition, order = overlay_definition
			else:
				order = None

			overlay = settings.load_overlay(overlay_definition)

			if "plugins" in overlay and "_disabled" in overlay["plugins"]:
				disabled_plugins = overlay["plugins"]["_disabled"]
				del overlay["plugins"]["_disabled"]
				disabled_from_overlays[name] = (disabled_plugins, order)

			settings_overlays[name] = overlay
			logger.debug("Found settings overlay on plugin {}".format(name))

	def handle_plugins_loaded(startup=False, initialize_implementations=True, force_reload=None):
		if not startup:
			return

		sorted_disabled_from_overlays = sorted([(key, value[0], value[1]) for key, value in disabled_from_overlays.items()], key=lambda x: (x[2] is None, x[2], x[0]))

		disabled_list = pm.plugin_disabled_list
		already_processed = []
		for name, addons, _ in sorted_disabled_from_overlays:
			if not name in disabled_list and not name.endswith("disabled"):
				for addon in addons:
					if addon in disabled_list:
						continue

					if addon in already_processed:
						logger.info("Plugin {} wants to disable plugin {}, but that was already processed".format(name, addon))

					if not addon in already_processed and not addon in disabled_list:
						disabled_list.append(addon)
						logger.info("Disabling plugin {} as defined by plugin {}".format(addon, name))
				already_processed.append(name)

	def handle_plugin_enabled(name, plugin):
		if name in settings_overlays:
			settings.add_overlay(settings_overlays[name])
			logger.info("Added settings overlay from plugin {}".format(name))

	pm.on_plugin_loaded = handle_plugin_loaded
	pm.on_plugins_loaded = handle_plugins_loaded
	pm.on_plugin_enabled = handle_plugin_enabled
	pm.reload_plugins(startup=True, initialize_implementations=False)
	return pm
示例#4
0
def init_logging(settings, use_logging_file=True, logging_file=None, default_config=None, debug=False, verbosity=0, uncaught_logger=None, uncaught_handler=None):
	"""Sets up logging."""

	import os

	from octoprint.util import dict_merge

	# default logging configuration
	if default_config is None:
		default_config = {
			"version": 1,
			"formatters": {
				"simple": {
					"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
				},
				"serial": {
					"format": "%(asctime)s - %(message)s"
				}
			},
			"handlers": {
				"console": {
					"class": "logging.StreamHandler",
					"level": "DEBUG",
					"formatter": "simple",
					"stream": "ext://sys.stdout"
				},
				"file": {
					"class": "octoprint.logging.handlers.OctoPrintLogHandler",
					"level": "DEBUG",
					"formatter": "simple",
					"when": "D",
					"backupCount": 6,
					"filename": os.path.join(settings.getBaseFolder("logs"), "octoprint.log")
				},
				"serialFile": {
					"class": "octoprint.logging.handlers.SerialLogHandler",
					"level": "DEBUG",
					"formatter": "serial",
					"backupCount": 3,
					"filename": os.path.join(settings.getBaseFolder("logs"), "serial.log"),
					"delay": True
				}
			},
			"loggers": {
				"SERIAL": {
					"level": "INFO",
					"handlers": ["serialFile"],
					"propagate": False
				},
				"octoprint": {
					"level": "INFO"
				},
				"octoprint.util": {
					"level": "INFO"
				},
				"octoprint.plugins": {
					"level": "INFO"
				}
			},
			"root": {
				"level": "WARN",
				"handlers": ["console", "file"]
			}
		}

	if debug or verbosity > 0:
		default_config["loggers"]["octoprint"]["level"] = "DEBUG"
		default_config["root"]["level"] = "INFO"
	if verbosity > 1:
		default_config["loggers"]["octoprint.plugins"]["level"] = "DEBUG"
	if verbosity > 2:
		default_config["root"]["level"] = "DEBUG"

	config = default_config
	if use_logging_file:
		# further logging configuration from file...
		if logging_file is None:
			logging_file = os.path.join(settings.getBaseFolder("base"), "logging.yaml")

		config_from_file = {}
		if os.path.exists(logging_file) and os.path.isfile(logging_file):
			import yaml
			with open(logging_file, "r") as f:
				config_from_file = yaml.safe_load(f)

		# we merge that with the default config
		if config_from_file is not None and isinstance(config_from_file, dict):
			config = dict_merge(default_config, config_from_file)

	# configure logging globally
	return set_logging_config(config, debug, verbosity, uncaught_logger, uncaught_handler)
示例#5
0
def init_pluginsystem(settings, safe_mode=False):
	"""Initializes the plugin manager based on the settings."""

	import os

	logger = log.getLogger(__name__)

	plugin_folders = [(os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "plugins")), True),
	                  settings.getBaseFolder("plugins")]
	plugin_entry_points = ["octoprint.plugin"]
	plugin_disabled_list = settings.get(["plugins", "_disabled"])

	plugin_validators = []
	if safe_mode:
		def validator(phase, plugin_info):
			if phase == "after_load":
				setattr(plugin_info, "safe_mode_victim", not plugin_info.bundled)
				setattr(plugin_info, "safe_mode_enabled", False)
			elif phase == "before_enable":
				if not plugin_info.bundled:
					setattr(plugin_info, "safe_mode_enabled", True)
					return False
			return True
		plugin_validators.append(validator)

	from octoprint.plugin import plugin_manager
	pm = plugin_manager(init=True,
	                    plugin_folders=plugin_folders,
	                    plugin_entry_points=plugin_entry_points,
	                    plugin_disabled_list=plugin_disabled_list,
	                    plugin_validators=plugin_validators)

	settings_overlays = dict()
	disabled_from_overlays = dict()

	def handle_plugin_loaded(name, plugin):
		if hasattr(plugin.instance, "__plugin_settings_overlay__"):
			plugin.needs_restart = True

			# plugin has a settings overlay, inject it
			overlay_definition = getattr(plugin.instance, "__plugin_settings_overlay__")
			if isinstance(overlay_definition, (tuple, list)):
				overlay_definition, order = overlay_definition
			else:
				order = None

			overlay = settings.load_overlay(overlay_definition)

			if "plugins" in overlay and "_disabled" in overlay["plugins"]:
				disabled_plugins = overlay["plugins"]["_disabled"]
				del overlay["plugins"]["_disabled"]
				disabled_from_overlays[name] = (disabled_plugins, order)

			settings_overlays[name] = overlay
			logger.debug("Found settings overlay on plugin {}".format(name))

	def handle_plugins_loaded(startup=False, initialize_implementations=True, force_reload=None):
		if not startup:
			return

		sorted_disabled_from_overlays = sorted([(key, value[0], value[1]) for key, value in disabled_from_overlays.items()], key=lambda x: (x[2] is None, x[2], x[0]))

		disabled_list = pm.plugin_disabled_list
		already_processed = []
		for name, addons, _ in sorted_disabled_from_overlays:
			if not name in disabled_list and not name.endswith("disabled"):
				for addon in addons:
					if addon in disabled_list:
						continue

					if addon in already_processed:
						logger.info("Plugin {} wants to disable plugin {}, but that was already processed".format(name, addon))

					if not addon in already_processed and not addon in disabled_list:
						disabled_list.append(addon)
						logger.info("Disabling plugin {} as defined by plugin {} through settings overlay".format(addon, name))
				already_processed.append(name)

	def handle_plugin_enabled(name, plugin):
		if name in settings_overlays:
			settings.add_overlay(settings_overlays[name])
			logger.info("Added settings overlay from plugin {}".format(name))

	pm.on_plugin_loaded = handle_plugin_loaded
	pm.on_plugins_loaded = handle_plugins_loaded
	pm.on_plugin_enabled = handle_plugin_enabled
	pm.reload_plugins(startup=True, initialize_implementations=False)
	return pm
示例#6
0
def init_logging(settings, use_logging_file=True, logging_file=None, default_config=None, debug=False, verbosity=0, uncaught_logger=None, uncaught_handler=None):
	"""Sets up logging."""

	import os

	from octoprint.util import dict_merge

	# default logging configuration
	if default_config is None:
		default_config = {
			"version": 1,
			"formatters": {
				"simple": {
					"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
				},
				"serial": {
					"format": "%(asctime)s - %(message)s"
				}
			},
			"handlers": {
				"console": {
					"class": "logging.StreamHandler",
					"level": "DEBUG",
					"formatter": "simple",
					"stream": "ext://sys.stdout"
				},
				"file": {
					"class": "octoprint.logging.handlers.OctoPrintLogHandler",
					"level": "DEBUG",
					"formatter": "simple",
					"when": "D",
					"backupCount": 6,
					"filename": os.path.join(settings.getBaseFolder("logs"), "octoprint.log")
				},
				"serialFile": {
					"class": "octoprint.logging.handlers.SerialLogHandler",
					"level": "DEBUG",
					"formatter": "serial",
					"backupCount": 3,
					"filename": os.path.join(settings.getBaseFolder("logs"), "serial.log")
				}
			},
			"loggers": {
				"SERIAL": {
					"level": "CRITICAL",
					"handlers": ["serialFile"],
					"propagate": False
				},
				"octoprint": {
					"level": "INFO"
				},
				"octoprint.util": {
					"level": "INFO"
				},
				"octoprint.plugins": {
					"level": "INFO"
				}
			},
			"root": {
				"level": "WARN",
				"handlers": ["console", "file"]
			}
		}

	if debug or verbosity > 0:
		default_config["loggers"]["octoprint"]["level"] = "DEBUG"
		default_config["root"]["level"] = "INFO"
	if verbosity > 1:
		default_config["loggers"]["octoprint.plugins"]["level"] = "DEBUG"
	if verbosity > 2:
		default_config["root"]["level"] = "DEBUG"

	config = default_config
	if use_logging_file:
		# further logging configuration from file...
		if logging_file is None:
			logging_file = os.path.join(settings.getBaseFolder("base"), "logging.yaml")

		config_from_file = {}
		if os.path.exists(logging_file) and os.path.isfile(logging_file):
			import yaml
			with open(logging_file, "r") as f:
				config_from_file = yaml.safe_load(f)

		# we merge that with the default config
		if config_from_file is not None and isinstance(config_from_file, dict):
			config = dict_merge(default_config, config_from_file)

	# configure logging globally
	return set_logging_config(config, debug, verbosity, uncaught_logger, uncaught_handler)
示例#7
0
def get_plugin_blacklist(settings, connectivity_checker=None):
	import requests
	import os
	import time
	import yaml

	from octoprint.util import bom_aware_open
	from octoprint.util.version import is_octoprint_compatible

	logger = log.getLogger(__name__ + ".startup")

	if connectivity_checker is not None and not connectivity_checker.online:
		logger.info("We don't appear to be online, not fetching plugin blacklist")
		return []

	def format_blacklist(entries):
		format_entry = lambda x: "{} ({})".format(x[0], x[1]) if isinstance(x, (list, tuple)) and len(x) == 2 \
			else "{} (any)".format(x)
		return ", ".join(map(format_entry, entries))

	def process_blacklist(entries):
		result = []

		if not isinstance(entries, list):
			return result

		for entry in entries:
			if not "plugin" in entry:
				continue

			if "octoversions" in entry and not is_octoprint_compatible(*entry["octoversions"]):
				continue

			if "version" in entry:
				logger.debug("Blacklisted plugin: {}, version: {}".format(entry["plugin"], entry["version"]))
				result.append((entry["plugin"], entry["version"]))
			elif "versions" in entry:
				logger.debug("Blacklisted plugin: {}, versions: {}".format(entry["plugin"], ", ".join(entry["versions"])))
				for version in entry["versions"]:
					result.append((entry["plugin"], version))
			else:
				logger.debug("Blacklisted plugin: {}".format(entry["plugin"]))
				result.append(entry["plugin"])

		return result

	def fetch_blacklist_from_cache(path, ttl):
		if not os.path.isfile(path):
			return None

		if os.stat(path).st_mtime + ttl < time.time():
			return None

		with bom_aware_open(path, encoding="utf-8", mode="r") as f:
			result = yaml.safe_load(f)

		if isinstance(result, list):
			return result

	def fetch_blacklist_from_url(url, timeout=3, cache=None):
		result = []
		try:
			r = requests.get(url, timeout=timeout)
			result = process_blacklist(r.json())

			if cache is not None:
				try:
					with bom_aware_open(cache, encoding="utf-8", mode="w") as f:
						yaml.safe_dump(result, f)
				except:
					logger.info("Fetched plugin blacklist but couldn't write it to its cache file.")
		except:
			logger.info("Unable to fetch plugin blacklist from {}, proceeding without it.".format(url))
		return result

	try:
		# first attempt to fetch from cache
		cache_path = os.path.join(settings.getBaseFolder("data"), "plugin_blacklist.yaml")
		ttl = settings.getInt(["server", "pluginBlacklist", "ttl"])
		blacklist = fetch_blacklist_from_cache(cache_path, ttl)

		if blacklist is None:
			# no result from the cache, let's fetch it fresh
			url = settings.get(["server", "pluginBlacklist", "url"])
			timeout = settings.getFloat(["server", "pluginBlacklist", "timeout"])
			blacklist = fetch_blacklist_from_url(url, timeout=timeout, cache=cache_path)

		if blacklist is None:
			# still now result, so no blacklist
			blacklist = []

		if blacklist:
			logger.info("Blacklist processing done, "
			            "adding {} blacklisted plugin versions: {}".format(len(blacklist),
			                                                               format_blacklist(blacklist)))
		else:
			logger.info("Blacklist processing done")

		return blacklist
	except:
		logger.exception("Something went wrong while processing the plugin blacklist. Proceeding without it.")
示例#8
0
def init_logging(
    settings,
    use_logging_file=True,
    logging_file=None,
    default_config=None,
    debug=False,
    verbosity=0,
    uncaught_logger=None,
    uncaught_handler=None,
):
    """Sets up logging."""

    import os

    from octoprint.util import dict_merge

    # default logging configuration
    if default_config is None:
        simple_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        default_config = {
            "version": 1,
            "formatters": {
                "simple": {
                    "format": simple_format
                },
                "colored": {
                    "()": "colorlog.ColoredFormatter",
                    "format": "%(log_color)s" + simple_format + "%(reset)s",
                    "reset": True,
                    "log_colors": {
                        "DEBUG": "cyan",
                        "INFO": "white",
                        "WARNING": "yellow",
                        "ERROR": "red",
                        "CRITICAL": "bold_red",
                    },
                },
                "serial": {
                    "format": "%(asctime)s - %(message)s"
                },
                "timings": {
                    "format": "%(asctime)s - %(message)s"
                },
                "timingscsv": {
                    "format": "%(asctime)s;%(func)s;%(timing)f"
                },
            },
            "handlers": {
                "console": {
                    "class":
                    "octoprint.logging.handlers.OctoPrintStreamHandler",
                    "level": "DEBUG",
                    "formatter": "colored",
                    "stream": "ext://sys.stdout",
                },
                "file": {
                    "class":
                    "octoprint.logging.handlers.OctoPrintLogHandler",
                    "level":
                    "DEBUG",
                    "formatter":
                    "simple",
                    "when":
                    "D",
                    "backupCount":
                    6,
                    "filename":
                    os.path.join(settings.getBaseFolder("logs"),
                                 "octoprint.log"),
                },
                "serialFile": {
                    "class":
                    "octoprint.logging.handlers.SerialLogHandler",
                    "level":
                    "DEBUG",
                    "formatter":
                    "serial",
                    "backupCount":
                    3,
                    "filename":
                    os.path.join(settings.getBaseFolder("logs"), "serial.log"),
                    "delay":
                    True,
                },
                "pluginTimingsFile": {
                    "class":
                    "octoprint.logging.handlers.PluginTimingsLogHandler",
                    "level":
                    "DEBUG",
                    "formatter":
                    "timings",
                    "backupCount":
                    3,
                    "filename":
                    os.path.join(settings.getBaseFolder("logs"),
                                 "plugintimings.log"),
                    "delay":
                    True,
                },
                "pluginTimingsCsvFile": {
                    "class":
                    "octoprint.logging.handlers.PluginTimingsLogHandler",
                    "level":
                    "DEBUG",
                    "formatter":
                    "timingscsv",
                    "backupCount":
                    3,
                    "filename":
                    os.path.join(settings.getBaseFolder("logs"),
                                 "plugintimings.csv"),
                    "delay":
                    True,
                },
            },
            "loggers": {
                "SERIAL": {
                    "level": "INFO",
                    "handlers": ["serialFile"],
                    "propagate": False,
                },
                "PLUGIN_TIMINGS": {
                    "level": "INFO",
                    "handlers": ["pluginTimingsFile", "pluginTimingsCsvFile"],
                    "propagate": False,
                },
                "PLUGIN_TIMINGS.octoprint.plugin": {
                    "level": "INFO"
                },
                "octoprint": {
                    "level": "INFO"
                },
                "octoprint.util": {
                    "level": "INFO"
                },
                "octoprint.plugins": {
                    "level": "INFO"
                },
            },
            "root": {
                "level": "WARN",
                "handlers": ["console", "file"]
            },
        }

    if debug or verbosity > 0:
        default_config["loggers"]["octoprint"]["level"] = "DEBUG"
        default_config["root"]["level"] = "INFO"
    if verbosity > 1:
        default_config["loggers"]["octoprint.plugins"]["level"] = "DEBUG"
    if verbosity > 2:
        default_config["root"]["level"] = "DEBUG"

    config = default_config
    if use_logging_file:
        # further logging configuration from file...
        if logging_file is None:
            logging_file = os.path.join(settings.getBaseFolder("base"),
                                        "logging.yaml")

        config_from_file = {}
        if os.path.exists(logging_file) and os.path.isfile(logging_file):
            import yaml

            with io.open(logging_file, "rt", encoding="utf-8") as f:
                config_from_file = yaml.safe_load(f)

        # we merge that with the default config
        if config_from_file is not None and isinstance(config_from_file, dict):
            config = dict_merge(default_config, config_from_file)

    # configure logging globally
    return set_logging_config(config, debug, verbosity, uncaught_logger,
                              uncaught_handler)