Merge branch 'master' into devel
This commit is contained in:
commit
65ae48d992
15 changed files with 491 additions and 174 deletions
|
|
@ -136,8 +136,16 @@ class FileManager(object):
|
|||
|
||||
def initialize(self):
|
||||
self.reload_plugins()
|
||||
for storage_type, storage_manager in self._storage_managers.items():
|
||||
self._determine_analysis_backlog(storage_type, storage_manager)
|
||||
|
||||
def worker():
|
||||
self._logger.info("Adding backlog items from all storage types to analysis queue...".format(**locals()))
|
||||
for storage_type, storage_manager in self._storage_managers.items():
|
||||
self._determine_analysis_backlog(storage_type, storage_manager)
|
||||
|
||||
import threading
|
||||
thread = threading.Thread(target=worker)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def reload_plugins(self):
|
||||
self._progress_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.ProgressPlugin)
|
||||
|
|
@ -150,13 +158,15 @@ class FileManager(object):
|
|||
self._slicing_progress_callbacks.remove(callback)
|
||||
|
||||
def _determine_analysis_backlog(self, storage_type, storage_manager):
|
||||
self._logger.info("Adding backlog items from {storage_type} to analysis queue".format(**locals()))
|
||||
counter = 0
|
||||
for entry, path, printer_profile in storage_manager.analysis_backlog:
|
||||
file_type = get_file_type(path)[-1]
|
||||
|
||||
# we'll use the default printer profile for the backlog since we don't know better
|
||||
queue_entry = QueueEntry(entry, file_type, storage_type, path, self._printer_profile_manager.get_default())
|
||||
self._analysis_queue.enqueue(queue_entry, high_priority=False)
|
||||
counter += 1
|
||||
self._logger.info("Added {counter} items from storage type \"{storage_type}\" to analysis queue".format(**locals()))
|
||||
|
||||
def add_storage(self, storage_type, storage_manager):
|
||||
self._storage_managers[storage_type] = storage_manager
|
||||
|
|
|
|||
|
|
@ -309,6 +309,41 @@ class LocalFileStorage(StorageInterface):
|
|||
|
||||
self._metadata_cache = pylru.lrucache(10)
|
||||
|
||||
self._old_metadata = None
|
||||
self._initialize_metadata()
|
||||
|
||||
def _initialize_metadata(self):
|
||||
self._logger.info("Initializing the file metadata for {}...".format(self.basefolder))
|
||||
|
||||
old_metadata_path = os.path.join(self.basefolder, "metadata.yaml")
|
||||
backup_path = os.path.join(self.basefolder, "metadata.yaml.backup")
|
||||
|
||||
if os.path.exists(old_metadata_path):
|
||||
# load the old metadata file
|
||||
try:
|
||||
with open(old_metadata_path) as f:
|
||||
import yaml
|
||||
self._old_metadata = yaml.safe_load(f)
|
||||
except:
|
||||
self._logger.exception("Error while loading old metadata file")
|
||||
|
||||
# make sure the metadata is initialized as far as possible
|
||||
self._list_folder(self.basefolder)
|
||||
|
||||
# rename the old metadata file
|
||||
self._old_metadata = None
|
||||
try:
|
||||
import shutil
|
||||
shutil.move(old_metadata_path, backup_path)
|
||||
except:
|
||||
self._logger.exception("Could not rename old metadata.yaml file")
|
||||
|
||||
else:
|
||||
# make sure the metadata is initialized as far as possible
|
||||
self._list_folder(self.basefolder)
|
||||
|
||||
self._logger.info("... file metadata for {} initialized successfully.".format(self.basefolder))
|
||||
|
||||
@property
|
||||
def analysis_backlog(self):
|
||||
for entry in self._analysis_backlog_generator():
|
||||
|
|
@ -919,12 +954,7 @@ class LocalFileStorage(StorageInterface):
|
|||
if entry in metadata and isinstance(metadata[entry], dict):
|
||||
entry_data = metadata[entry]
|
||||
else:
|
||||
entry_data = dict(
|
||||
hash=self._create_hash(entry_path),
|
||||
links=[],
|
||||
notes=[]
|
||||
)
|
||||
metadata[entry] = entry_data
|
||||
entry_data = self._add_basic_metadata(path, entry, save=False, metadata=metadata)
|
||||
metadata_dirty = True
|
||||
|
||||
# TODO extract model hash from source if possible to recreate link
|
||||
|
|
@ -959,6 +989,31 @@ class LocalFileStorage(StorageInterface):
|
|||
|
||||
return result
|
||||
|
||||
def _add_basic_metadata(self, path, entry, additional_metadata=None, save=True, metadata=None):
|
||||
if additional_metadata is None:
|
||||
additional_metadata = dict()
|
||||
|
||||
if metadata is None:
|
||||
metadata = self._get_metadata(path)
|
||||
|
||||
entry_data = dict(
|
||||
hash=self._create_hash(os.path.join(path, entry)),
|
||||
links=[],
|
||||
notes=[]
|
||||
)
|
||||
|
||||
if path == self.basefolder and self._old_metadata is not None and entry in self._old_metadata and "gcodeAnalysis" in self._old_metadata[entry]:
|
||||
# if there is still old metadata available and that contains an analysis for this file, use it!
|
||||
entry_data["analysis"] = self._old_metadata[entry]["gcodeAnalysis"]
|
||||
|
||||
entry_data.update(additional_metadata)
|
||||
metadata[entry] = entry_data
|
||||
|
||||
if save:
|
||||
self._save_metadata(path, metadata)
|
||||
|
||||
return entry_data
|
||||
|
||||
def _create_hash(self, path):
|
||||
import hashlib
|
||||
|
||||
|
|
@ -993,16 +1048,22 @@ class LocalFileStorage(StorageInterface):
|
|||
def _save_metadata(self, path, metadata):
|
||||
metadata_path = os.path.join(path, ".metadata.yaml")
|
||||
|
||||
fh, metadata_temporary_path = tempfile.mkstemp()
|
||||
os.close(fh)
|
||||
|
||||
with self._metadata_lock:
|
||||
try:
|
||||
with open(metadata_temporary_path, "w") as f:
|
||||
import yaml
|
||||
yaml.safe_dump(metadata, stream=f, default_flow_style=False, indent=" ", allow_unicode=True)
|
||||
import yaml
|
||||
import shutil
|
||||
shutil.move(metadata_temporary_path, metadata_path)
|
||||
|
||||
file_obj = tempfile.NamedTemporaryFile(delete=False)
|
||||
try:
|
||||
yaml.safe_dump(metadata, stream=file_obj, default_flow_style=False, indent=" ", allow_unicode=True)
|
||||
file_obj.close()
|
||||
shutil.move(file_obj.name, metadata_path)
|
||||
finally:
|
||||
try:
|
||||
if os.path.exists(file_obj.name):
|
||||
os.remove(file_obj.name)
|
||||
except Exception as e:
|
||||
self._logger.warn("Could not delete file {}: {}".format(file_obj.name, str(e)))
|
||||
except:
|
||||
self._logger.exception("Error while writing .metadata.yaml to {path}".format(**locals()))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -281,6 +281,7 @@ class PluginSettings(object):
|
|||
defaults = dict()
|
||||
self.defaults = dict(plugins=dict())
|
||||
self.defaults["plugins"][plugin_key] = defaults
|
||||
self.defaults["plugins"][plugin_key]["_config_version"] = None
|
||||
|
||||
if get_preprocessors is None:
|
||||
get_preprocessors = dict()
|
||||
|
|
@ -309,11 +310,17 @@ class PluginSettings(object):
|
|||
return result
|
||||
|
||||
def add_getter_kwargs(kwargs):
|
||||
kwargs.update(defaults=self.defaults, preprocessors=self.get_preprocessors)
|
||||
if not "defaults" in kwargs:
|
||||
kwargs.update(defaults=self.defaults)
|
||||
if not "preprocessors" in kwargs:
|
||||
kwargs.update(preprocessors=self.get_preprocessors)
|
||||
return kwargs
|
||||
|
||||
def add_setter_kwargs(kwargs):
|
||||
kwargs.update(defaults=self.defaults, preprocessors=self.set_preprocessors)
|
||||
if not "defaults" in kwargs:
|
||||
kwargs.update(defaults=self.defaults)
|
||||
if not "preprocessors" in kwargs:
|
||||
kwargs.update(preprocessors=self.set_preprocessors)
|
||||
return kwargs
|
||||
|
||||
self.access_methods = dict(
|
||||
|
|
|
|||
|
|
@ -428,7 +428,9 @@ class PluginManager(object):
|
|||
It is able to discover plugins both through possible file system locations as well as customizable entry points.
|
||||
"""
|
||||
|
||||
def __init__(self, plugin_folders, plugin_types, plugin_entry_points, logging_prefix=None, plugin_disabled_list=None, plugin_restart_needing_hooks=None, plugin_obsolete_hooks=None, plugin_validators=None):
|
||||
def __init__(self, plugin_folders, plugin_types, plugin_entry_points, logging_prefix=None,
|
||||
plugin_disabled_list=None, plugin_restart_needing_hooks=None, plugin_obsolete_hooks=None,
|
||||
plugin_validators=None):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
if logging_prefix is None:
|
||||
|
|
@ -453,6 +455,8 @@ class PluginManager(object):
|
|||
|
||||
self.implementation_injects = dict()
|
||||
self.implementation_inject_factories = []
|
||||
self.implementation_pre_inits = []
|
||||
self.implementation_post_inits = []
|
||||
|
||||
self.on_plugin_loaded = lambda *args, **kwargs: None
|
||||
self.on_plugin_unloaded = lambda *args, **kwargs: None
|
||||
|
|
@ -849,27 +853,35 @@ class PluginManager(object):
|
|||
return False
|
||||
return hook in self.plugin_obsolete_hooks
|
||||
|
||||
def initialize_implementations(self, additional_injects=None, additional_inject_factories=None):
|
||||
def initialize_implementations(self, additional_injects=None, additional_inject_factories=None, additional_pre_inits=None, additional_post_inits=None):
|
||||
for name, plugin in self.enabled_plugins.items():
|
||||
self.initialize_implementation_of_plugin(name, plugin,
|
||||
additional_injects=additional_injects,
|
||||
additional_inject_factories=additional_inject_factories)
|
||||
additional_inject_factories=additional_inject_factories,
|
||||
additional_pre_inits=additional_pre_inits,
|
||||
additional_post_inits=additional_post_inits)
|
||||
|
||||
self.logger.info("Initialized {count} plugin(s)".format(count=len(self.plugin_implementations)))
|
||||
|
||||
def initialize_implementation_of_plugin(self, name, plugin, additional_injects=None, additional_inject_factories=None):
|
||||
def initialize_implementation_of_plugin(self, name, plugin, additional_injects=None, additional_inject_factories=None, additional_pre_inits=None, additional_post_inits=None):
|
||||
if plugin.implementation is None:
|
||||
return
|
||||
|
||||
return self.initialize_implementation(name, plugin, plugin.implementation,
|
||||
additional_injects=additional_injects,
|
||||
additional_inject_factories=additional_inject_factories)
|
||||
additional_inject_factories=additional_inject_factories,
|
||||
additional_pre_inits=additional_pre_inits,
|
||||
additional_post_inits=additional_post_inits)
|
||||
|
||||
def initialize_implementation(self, name, plugin, implementation, additional_injects=None, additional_inject_factories=None):
|
||||
def initialize_implementation(self, name, plugin, implementation, additional_injects=None, additional_inject_factories=None, additional_pre_inits=None, additional_post_inits=None):
|
||||
if additional_injects is None:
|
||||
additional_injects = dict()
|
||||
if additional_inject_factories is None:
|
||||
additional_inject_factories = []
|
||||
if additional_pre_inits is None:
|
||||
additional_pre_inits = []
|
||||
if additional_post_inits is None:
|
||||
additional_post_inits = []
|
||||
|
||||
injects = self.implementation_injects
|
||||
injects.update(additional_injects)
|
||||
|
|
@ -877,6 +889,12 @@ class PluginManager(object):
|
|||
inject_factories = self.implementation_inject_factories
|
||||
inject_factories += additional_inject_factories
|
||||
|
||||
pre_inits = self.implementation_pre_inits
|
||||
pre_inits += additional_pre_inits
|
||||
|
||||
post_inits = self.implementation_post_inits
|
||||
post_inits += additional_post_inits
|
||||
|
||||
try:
|
||||
kwargs = dict(injects)
|
||||
|
||||
|
|
@ -904,8 +922,16 @@ class PluginManager(object):
|
|||
for arg, value in return_value.items():
|
||||
setattr(implementation, "_" + arg, value)
|
||||
|
||||
# execute any additional pre init methods
|
||||
for pre_init in pre_inits:
|
||||
pre_init(name, implementation)
|
||||
|
||||
implementation.initialize()
|
||||
|
||||
# execute any additional post init methods
|
||||
for post_init in post_inits:
|
||||
post_init(name, implementation)
|
||||
|
||||
except Exception as e:
|
||||
self._deactivate_plugin(name, plugin)
|
||||
plugin.enabled = False
|
||||
|
|
@ -1189,13 +1215,13 @@ class RestartNeedingPlugin(Plugin):
|
|||
|
||||
class PluginNeedsRestart(BaseException):
|
||||
def __init__(self, name):
|
||||
super(BaseException, self).__init__()
|
||||
BaseException.__init__(self)
|
||||
self.name = name
|
||||
self.message = "Plugin {name} cannot be enabled or disabled after system startup".format(**locals())
|
||||
|
||||
class PluginLifecycleException(BaseException):
|
||||
def __init__(self, name, reason, message):
|
||||
super(BaseException, self).__init__()
|
||||
BaseException.__init__(self)
|
||||
self.name = name
|
||||
self.reason = reason
|
||||
|
||||
|
|
@ -1206,7 +1232,7 @@ class PluginLifecycleException(BaseException):
|
|||
|
||||
class PluginCantInitialize(PluginLifecycleException):
|
||||
def __init__(self, name, reason):
|
||||
super(PluginLifecycleException, self).__init__(name, reason, "Plugin {name} cannot be initialized: {reason}")
|
||||
PluginLifecycleException.__init__(self, name, reason, "Plugin {name} cannot be initialized: {reason}")
|
||||
|
||||
class PluginCantEnable(PluginLifecycleException):
|
||||
def __init__(self, name, reason):
|
||||
|
|
@ -1214,4 +1240,4 @@ class PluginCantEnable(PluginLifecycleException):
|
|||
|
||||
class PluginCantDisable(PluginLifecycleException):
|
||||
def __init__(self, name, reason):
|
||||
super(PluginLifecycleException, self).__init__(name, reason, "Plugin {name} cannot be disabled: {reason}")
|
||||
PluginLifecycleException.__init__(self, name, reason, "Plugin {name} cannot be disabled: {reason}")
|
||||
|
|
|
|||
|
|
@ -718,7 +718,7 @@ class SettingsPlugin(OctoPrintPlugin):
|
|||
def on_settings_save(self, data):
|
||||
old_flag = self._settings.get_boolean(["sub", "some_flag"])
|
||||
|
||||
super(MySettingsPlugin, self).on_settings_save(data)
|
||||
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
|
||||
|
||||
new_flag = self._settings.get_boolean(["sub", "some_flag"])
|
||||
if old_flag != new_flag:
|
||||
|
|
@ -830,6 +830,41 @@ class SettingsPlugin(OctoPrintPlugin):
|
|||
"""
|
||||
return dict(), dict()
|
||||
|
||||
def get_settings_version(self):
|
||||
"""
|
||||
Retrieves the settings format version of the plugin.
|
||||
|
||||
Use this to have OctoPrint trigger your migration function if it detects an outdated settings version in
|
||||
config.yaml.
|
||||
|
||||
Returns:
|
||||
int or None: an int signifying the current settings format, should be incremented by plugins whenever there
|
||||
are backwards incompatible changes. Returning None here disables the version tracking for the
|
||||
plugin's configuration.
|
||||
"""
|
||||
return None
|
||||
|
||||
def on_settings_migrate(self, target, current):
|
||||
"""
|
||||
Called by OctoPrint if it detects that the installed version of the plugin necessitates a higher settings version
|
||||
than the one currently stored in _config.yaml. Will also be called if the settings data stored in config.yaml
|
||||
doesn't have version information, in which case the ``current`` parameter will be None.
|
||||
|
||||
Your plugin's implementation should take care of migrating any data by utilizing self._settings. OctoPrint
|
||||
will take care of saving any changes to disk by calling `self._settings.save()` after returning from this method.
|
||||
|
||||
This method will be called before your plugin's :func:`initialize` method, but with all injections already
|
||||
having taken place. You can therefore depend on the configuration having been migrated by the time :func:`initialize`
|
||||
is called.
|
||||
|
||||
Arguments:
|
||||
target (int): The settings format version the plugin requires, this should always be the same value as
|
||||
returned by :func:`get_settings_version`.
|
||||
current (int or None): The settings format version as currently stored in config.yaml. May be None if
|
||||
no version information can be found.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class EventHandlerPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
|
|||
def on_settings_save(self, data):
|
||||
old_debug_logging = self._settings.get_boolean(["debug_logging"])
|
||||
|
||||
super(CuraPlugin, self).on_settings_save(data)
|
||||
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
|
||||
|
||||
new_debug_logging = self._settings.get_boolean(["debug_logging"])
|
||||
if old_debug_logging != new_debug_logging:
|
||||
|
|
@ -410,4 +410,4 @@ __plugin_author__ = "Gina Häußge"
|
|||
__plugin_url__ = "https://github.com/foosel/OctoPrint/wiki/Plugin:-Cura"
|
||||
__plugin_description__ = "Adds support for slicing via CuraEngine from within OctoPrint"
|
||||
__plugin_license__ = "AGPLv3"
|
||||
__plugin_implementation__ = CuraPlugin()
|
||||
__plugin_implementation__ = CuraPlugin()
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
|
|||
result.append(self._to_external_representation(plugin))
|
||||
|
||||
if "refresh_repository" in request.values and request.values["refresh_repository"] in valid_boolean_trues:
|
||||
self._refresh_repository()
|
||||
self._repository_available = self._refresh_repository()
|
||||
|
||||
return jsonify(plugins=result, repository=dict(available=self._repository_available, plugins=self._repository_plugins), os=self._get_os(), octoprint=self._get_octoprint_version())
|
||||
|
||||
|
|
@ -141,6 +141,10 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
|
|||
if not admin_permission.can():
|
||||
return make_response("Insufficient rights", 403)
|
||||
|
||||
if self._printer.is_printing() or self._printer.is_paused():
|
||||
# do not update while a print job is running
|
||||
return make_response("Printer is currently printing or paused", 409)
|
||||
|
||||
if command == "install":
|
||||
url = data["url"]
|
||||
plugin_name = data["plugin"] if "plugin" in data else None
|
||||
|
|
@ -165,10 +169,6 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
|
|||
plugin = self._plugin_manager.plugins[plugin_name]
|
||||
return self.command_toggle(plugin, command)
|
||||
|
||||
elif command == "refresh_repository":
|
||||
self._repository_available = self._refresh_repository()
|
||||
return jsonify(repository=dict(available=self._repository_available, plugins=self._repository_plugins))
|
||||
|
||||
def command_install(self, url=None, path=None, force=False, reinstall=None, dependency_links=False):
|
||||
if url is not None:
|
||||
pip_args = ["install", sarge.shell_quote(url)]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ $(function() {
|
|||
|
||||
self.loginState = parameters[0];
|
||||
self.settingsViewModel = parameters[1];
|
||||
self.printerState = parameters[2];
|
||||
|
||||
self.plugins = new ItemListHelper(
|
||||
"plugin.pluginmanager.installedplugins",
|
||||
|
|
@ -76,6 +77,22 @@ $(function() {
|
|||
self.workingDialog = undefined;
|
||||
self.workingOutput = undefined;
|
||||
|
||||
self.enableManagement = ko.computed(function() {
|
||||
return !self.printerState.isPrinting();
|
||||
});
|
||||
|
||||
self.enableToggle = function(data) {
|
||||
return self.enableManagement() && data.key != 'pluginmanager';
|
||||
};
|
||||
|
||||
self.enableUninstall = function(data) {
|
||||
return self.enableManagement() && !data.bundled && data.key != 'pluginmanager' && !data.pending_uninstall;
|
||||
};
|
||||
|
||||
self.enableRepoInstall = function(data) {
|
||||
return self.enableManagement() && self.isCompatible(data);
|
||||
};
|
||||
|
||||
self.invalidUrl = ko.computed(function() {
|
||||
var url = self.installUrl();
|
||||
return url !== undefined && url.trim() != "" && !(_.startsWith(url.toLocaleLowerCase(), "http://") || _.startsWith(url.toLocaleLowerCase(), "https://"));
|
||||
|
|
@ -83,7 +100,7 @@ $(function() {
|
|||
|
||||
self.enableUrlInstall = ko.computed(function() {
|
||||
var url = self.installUrl();
|
||||
return url !== undefined && url.trim() != "" && !self.invalidUrl();
|
||||
return self.enableManagement() && url !== undefined && url.trim() != "" && !self.invalidUrl();
|
||||
});
|
||||
|
||||
self.invalidArchive = ko.computed(function() {
|
||||
|
|
@ -93,7 +110,7 @@ $(function() {
|
|||
|
||||
self.enableArchiveInstall = ko.computed(function() {
|
||||
var name = self.uploadFilename();
|
||||
return name !== undefined && name.trim() != "" && !self.invalidArchive();
|
||||
return self.enableManagement() && name !== undefined && name.trim() != "" && !self.invalidArchive();
|
||||
});
|
||||
|
||||
self.uploadElement.fileupload({
|
||||
|
|
@ -187,6 +204,10 @@ $(function() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!self.enableManagement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.key == "pluginmanager") return;
|
||||
|
||||
var command = self._getToggleCommand(data);
|
||||
|
|
@ -217,6 +238,10 @@ $(function() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!self.enableManagement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.installed(data)) {
|
||||
self.installPlugin(data.archive, data.title, data.id, data.follow_dependency_links || self.followDependencyLinks());
|
||||
} else {
|
||||
|
|
@ -229,6 +254,10 @@ $(function() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!self.enableManagement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (url === undefined) {
|
||||
url = self.installUrl();
|
||||
}
|
||||
|
|
@ -279,6 +308,10 @@ $(function() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!self.enableManagement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.bundled) return;
|
||||
if (data.key == "pluginmanager") return;
|
||||
|
||||
|
|
@ -305,9 +338,7 @@ $(function() {
|
|||
return;
|
||||
}
|
||||
|
||||
self._postCommand("refresh_repository", {}, function(data) {
|
||||
self._fromRepositoryResponse(data.repository);
|
||||
})
|
||||
self.requestData(true);
|
||||
};
|
||||
|
||||
self.installed = function(data) {
|
||||
|
|
@ -420,7 +451,7 @@ $(function() {
|
|||
|
||||
self.toggleButtonCss = function(data) {
|
||||
var icon = self._getToggleCommand(data) == "enable" ? "icon-circle-blank" : "icon-circle";
|
||||
var disabled = (data.key == "pluginmanager") ? " disabled" : "";
|
||||
var disabled = (self.enableToggle(data)) ? "" : " disabled";
|
||||
|
||||
return icon + disabled;
|
||||
};
|
||||
|
|
@ -578,5 +609,5 @@ $(function() {
|
|||
}
|
||||
|
||||
// view model class, parameters for constructor, container to bind to
|
||||
ADDITIONAL_VIEWMODELS.push([PluginManagerViewModel, ["loginStateViewModel", "settingsViewModel"], "#settings_plugin_pluginmanager"]);
|
||||
ADDITIONAL_VIEWMODELS.push([PluginManagerViewModel, ["loginStateViewModel", "settingsViewModel", "printerStateViewModel"], "#settings_plugin_pluginmanager"]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
{% macro pluginmanager_printing() %}
|
||||
<div class="alert" data-bind="visible: !enableManagement()">
|
||||
{{ _('Take note that all plugin management functionality is disabled while your printer is printing.') }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{{ pluginmanager_printing() }}
|
||||
|
||||
<h3>{{ _('Installed Plugins') }}</h3>
|
||||
|
||||
<table class="table table-striped table-hover table-condensed table-hover">
|
||||
|
|
@ -20,7 +28,7 @@
|
|||
</div>
|
||||
</td>
|
||||
<td class="settings_plugin_plugin_manager_plugins_actions">
|
||||
<a href="#" data-bind="css: $root.toggleButtonCss($data), attr: {title: $root.toggleButtonTitle($data)}, enable: key != 'pluginmanager', click: function() { $root.togglePlugin($data) }"></a> | <a href="#" class="icon-trash" title="{{ _('Uninstall Plugin') }}" data-bind="css: {disabled: bundled || key == 'pluginmanager' || pending_uninstall}, enable: !bundled && key != 'pluginmanager' && !pending_uninstall, click: function() { $root.uninstallPlugin($data) }"></a>
|
||||
<a href="#" data-bind="css: $root.toggleButtonCss($data), attr: {title: $root.toggleButtonTitle($data)}, enable: $root.enableToggle($data), click: function() { $root.togglePlugin($data) }"></a> | <a href="#" class="icon-trash" title="{{ _('Uninstall Plugin') }}" data-bind="css: {disabled: !$root.enableUninstall($data)}, enable: $root.enableUninstall($data), click: function() { $root.uninstallPlugin($data) }"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -58,6 +66,7 @@
|
|||
<h3>{{ _('Install new Plugins...') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ pluginmanager_printing() }}
|
||||
<h4 style="position: relative">
|
||||
{{ _('... from the <a href="%(url)s" target="_blank">Plugin Repository</a>', url='http://plugins.octoprint.org') }}
|
||||
<a class="dropdown-toggle pull-right" data-toggle="dropdown" href="#">
|
||||
|
|
@ -94,7 +103,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="span3">
|
||||
<button class="btn btn-primary btn-block" data-bind="enable: $root.isCompatible($data), css: {disabled: !$root.isCompatible($data)}, click: function() { if ($root.isCompatible($data)) { $root.installFromRepository($data); } else { return false; } }"><i class="icon-add"></i> <span data-bind="text: $root.installButtonText($data)"></span></button>
|
||||
<button class="btn btn-primary btn-block" data-bind="enable: $root.enableRepoInstall($data), css: {disabled: !$root.enableRepoInstall($data)}, click: function() { if ($root.enableRepoInstall($data)) { $root.installFromRepository($data); } else { return false; } }"><i class="icon-add"></i> <span data-bind="text: $root.installButtonText($data)"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
|
||||
def refresh_checks(name, plugin):
|
||||
self._refresh_configured_checks = True
|
||||
self._send_client_message("update_versions")
|
||||
|
||||
self._plugin_lifecycle_manager.add_callback("enabled", refresh_checks)
|
||||
self._plugin_lifecycle_manager.add_callback("disabled", refresh_checks)
|
||||
|
||||
|
|
@ -86,12 +88,76 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
}
|
||||
|
||||
def on_settings_save(self, data):
|
||||
super(SoftwareUpdatePlugin, self).on_settings_save(data)
|
||||
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
|
||||
self._version_cache_ttl = self._settings.get_int(["cache_ttl"]) * 60
|
||||
|
||||
def get_settings_version(self):
|
||||
return 1
|
||||
|
||||
def on_settings_migrate(self, target, current=None):
|
||||
if current is None:
|
||||
# there might be some left over data from the time we still persisted everything to settings,
|
||||
# even the stuff that shouldn't be persisted but always provided by the hook - let's
|
||||
# clean up
|
||||
|
||||
# take care of the octoprint entry
|
||||
configured_checks = self._settings.get(["checks"], merged=True)
|
||||
octoprint_check = dict(configured_checks["octoprint"])
|
||||
if "type" in octoprint_check and not octoprint_check["type"] == "github_commit":
|
||||
deletables=["current"]
|
||||
else:
|
||||
deletables=[]
|
||||
octoprint_check = self._clean_settings_check("octoprint", octoprint_check, self.get_settings_defaults()["checks"]["octoprint"], delete=deletables, save=False)
|
||||
configured_checks["octoprint"] = octoprint_check
|
||||
|
||||
# and the hooks
|
||||
update_check_hooks = self._plugin_manager.get_hooks("octoprint.plugin.softwareupdate.check_config")
|
||||
for name, hook in update_check_hooks.items():
|
||||
try:
|
||||
hook_checks = hook()
|
||||
except:
|
||||
self._logger.exception("Error while retrieving update information from plugin {name}".format(**locals()))
|
||||
else:
|
||||
for key, data in hook_checks.items():
|
||||
if key in configured_checks:
|
||||
settings_check = dict(configured_checks[key])
|
||||
merged = dict_merge(data, settings_check)
|
||||
if "type" in merged and not merged["type"] == "github_commit":
|
||||
deletables = ["current", "displayVersion"]
|
||||
else:
|
||||
deletables = []
|
||||
|
||||
self._clean_settings_check(key, settings_check, data, delete=deletables, save=False)
|
||||
|
||||
def _clean_settings_check(self, key, data, defaults, delete=None, save=True):
|
||||
if delete is None:
|
||||
delete = []
|
||||
|
||||
for k, v in data.items():
|
||||
if k in defaults and defaults[k] == data[k]:
|
||||
del data[k]
|
||||
|
||||
for k in delete:
|
||||
if k in data:
|
||||
del data[k]
|
||||
|
||||
dummy_defaults = dict(plugins=dict())
|
||||
dummy_defaults["plugins"][self._identifier] = dict(checks=dict())
|
||||
dummy_defaults["plugins"][self._identifier]["checks"][key] = defaults
|
||||
if len(data):
|
||||
self._settings.set(["checks", key], data, defaults=dummy_defaults)
|
||||
else:
|
||||
self._settings.set(["checks", key], None, defaults=dummy_defaults)
|
||||
|
||||
if save:
|
||||
self._settings.save()
|
||||
|
||||
return data
|
||||
|
||||
#~~ BluePrint API
|
||||
|
||||
@octoprint.plugin.BlueprintPlugin.route("/check", methods=["GET"])
|
||||
@restricted_access
|
||||
def check_for_update(self):
|
||||
if "check" in flask.request.values:
|
||||
check_targets = map(str.strip, flask.request.values["check"].split(","))
|
||||
|
|
@ -101,7 +167,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
if "force" in flask.request.values and flask.request.values["force"] in octoprint.settings.valid_boolean_trues:
|
||||
force = True
|
||||
else:
|
||||
force=False
|
||||
force = False
|
||||
|
||||
try:
|
||||
information, update_available, update_possible = self.get_current_versions(check_targets=check_targets, force=force)
|
||||
|
|
@ -116,10 +182,10 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
def perform_update(self):
|
||||
if self._printer.is_printing() or self._printer.is_paused():
|
||||
# do not update while a print job is running
|
||||
flask.make_response("Printer is currently printing or paused", 409)
|
||||
return flask.make_response("Printer is currently printing or paused", 409)
|
||||
|
||||
if not "application/json" in flask.request.headers["Content-Type"]:
|
||||
flask.make_response("Expected content-type JSON", 400)
|
||||
return flask.make_response("Expected content-type JSON", 400)
|
||||
|
||||
json_data = flask.request.json
|
||||
|
||||
|
|
@ -128,9 +194,8 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
else:
|
||||
check_targets = None
|
||||
|
||||
if "force" in json_data:
|
||||
from octoprint.settings import valid_boolean_trues
|
||||
force = (json_data["force"] in valid_boolean_trues)
|
||||
if "force" in json_data and json_data["force"] in octoprint.settings.valid_boolean_trues:
|
||||
force = True
|
||||
else:
|
||||
force = False
|
||||
|
||||
|
|
@ -378,15 +443,14 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
|
||||
# persist the new version if necessary for check type
|
||||
if check["type"] == "github_commit":
|
||||
checks = self._settings.get(["checks"], merged=True)
|
||||
if target in checks:
|
||||
# TODO make this cleaner, right now it saves too much to disk
|
||||
checks[target]["current"] = target_version
|
||||
self._settings.set(["checks"], checks)
|
||||
dummy_default = dict(plugins=dict())
|
||||
dummy_default["plugins"][self._identifier] = dict(checks=dict())
|
||||
dummy_default["plugins"][self._identifier]["checks"][target] = dict(current=None)
|
||||
self._settings.set(["checks", target, "current"], target_version, defaults=dummy_default)
|
||||
|
||||
# we have to save here (even though that makes us save quite often) since otherwise the next
|
||||
# load will overwrite our changes we just made
|
||||
self._settings.save()
|
||||
# we have to save here (even though that makes us save quite often) since otherwise the next
|
||||
# load will overwrite our changes we just made
|
||||
self._settings.save()
|
||||
|
||||
return target_error, target_result
|
||||
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ $(function() {
|
|||
};
|
||||
|
||||
self._showPopup = function(options, eventListeners) {
|
||||
if (self.popup !== undefined) {
|
||||
self.popup.remove();
|
||||
}
|
||||
self._closePopup();
|
||||
self.popup = new PNotify(options);
|
||||
|
||||
if (eventListeners) {
|
||||
|
|
@ -65,6 +63,12 @@ $(function() {
|
|||
}
|
||||
};
|
||||
|
||||
self._closePopup = function() {
|
||||
if (self.popup !== undefined) {
|
||||
self.popup.remove();
|
||||
}
|
||||
};
|
||||
|
||||
self.showPluginSettings = function() {
|
||||
self._copyConfig();
|
||||
self.configurationDialog.modal();
|
||||
|
|
@ -89,6 +93,86 @@ $(function() {
|
|||
self.config_cacheTtl(self.settings.settings.plugins.softwareupdate.cache_ttl());
|
||||
};
|
||||
|
||||
self.fromCheckResponse = function(data, ignoreSeen, showIfNothingNew) {
|
||||
var versions = [];
|
||||
_.each(data.information, function(value, key) {
|
||||
value["key"] = key;
|
||||
|
||||
if (!value.hasOwnProperty("displayName") || value.displayName == "") {
|
||||
value.displayName = value.key;
|
||||
}
|
||||
if (!value.hasOwnProperty("displayVersion") || value.displayVersion == "") {
|
||||
value.displayVersion = value.information.local.name;
|
||||
}
|
||||
|
||||
versions.push(value);
|
||||
});
|
||||
self.versions.updateItems(versions);
|
||||
|
||||
if (data.status == "updateAvailable" || data.status == "updatePossible") {
|
||||
var text = gettext("There are updates available for the following components:");
|
||||
|
||||
text += "<ul>";
|
||||
_.each(self.versions.items(), function(update_info) {
|
||||
if (update_info.updateAvailable) {
|
||||
var displayName = update_info.key;
|
||||
if (update_info.hasOwnProperty("displayName")) {
|
||||
displayName = update_info.displayName;
|
||||
}
|
||||
text += "<li>" + displayName + (update_info.updatePossible ? " <i class=\"icon-ok\"></i>" : "") + "</li>";
|
||||
}
|
||||
});
|
||||
text += "</ul>";
|
||||
|
||||
text += "<small>" + gettext("Those components marked with <i class=\"icon-ok\"></i> can be updated directly.") + "</small>";
|
||||
|
||||
var options = {
|
||||
title: gettext("Update Available"),
|
||||
text: text,
|
||||
hide: false
|
||||
};
|
||||
var eventListeners = {};
|
||||
|
||||
if (data.status == "updatePossible" && self.loginState.isAdmin()) {
|
||||
// if user is admin, add action buttons
|
||||
options["confirm"] = {
|
||||
confirm: true,
|
||||
buttons: [{
|
||||
text: gettext("Ignore"),
|
||||
click: function() {
|
||||
self._markNotificationAsSeen(data.information);
|
||||
self._showPopup({
|
||||
text: gettext("You can make this message display again via \"Settings\" > \"SoftwareUpdate\" > \"Check for update now\"")
|
||||
});
|
||||
}
|
||||
}, {
|
||||
text: gettext("Update now"),
|
||||
addClass: "btn-primary",
|
||||
click: self.update
|
||||
}]
|
||||
};
|
||||
options["buttons"] = {
|
||||
closer: false,
|
||||
sticker: false
|
||||
};
|
||||
}
|
||||
|
||||
if (ignoreSeen || !self._hasNotificationBeenSeen(data.information)) {
|
||||
self._showPopup(options, eventListeners);
|
||||
}
|
||||
} else if (data.status == "current") {
|
||||
if (showIfNothingNew) {
|
||||
self._showPopup({
|
||||
title: gettext("Everything is up-to-date"),
|
||||
hide: false,
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
self._closePopup();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.performCheck = function(showIfNothingNew, force, ignoreSeen) {
|
||||
if (!self.loginState.isUser()) return;
|
||||
|
||||
|
|
@ -102,79 +186,7 @@ $(function() {
|
|||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
var versions = [];
|
||||
_.each(data.information, function(value, key) {
|
||||
value["key"] = key;
|
||||
|
||||
if (!value.hasOwnProperty("displayName") || value.displayName == "") {
|
||||
value.displayName = value.key;
|
||||
}
|
||||
if (!value.hasOwnProperty("displayVersion") || value.displayVersion == "") {
|
||||
value.displayVersion = value.information.local.name;
|
||||
}
|
||||
|
||||
versions.push(value);
|
||||
});
|
||||
self.versions.updateItems(versions);
|
||||
|
||||
if (data.status == "updateAvailable" || data.status == "updatePossible") {
|
||||
var text = gettext("There are updates available for the following components:");
|
||||
|
||||
text += "<ul>";
|
||||
_.each(self.versions.items(), function(update_info) {
|
||||
if (update_info.updateAvailable) {
|
||||
var displayName = update_info.key;
|
||||
if (update_info.hasOwnProperty("displayName")) {
|
||||
displayName = update_info.displayName;
|
||||
}
|
||||
text += "<li>" + displayName + (update_info.updatePossible ? " <i class=\"icon-ok\"></i>" : "") + "</li>";
|
||||
}
|
||||
});
|
||||
text += "</ul>";
|
||||
|
||||
text += "<small>" + gettext("Those components marked with <i class=\"icon-ok\"></i> can be updated directly.") + "</small>";
|
||||
|
||||
var options = {
|
||||
title: gettext("Update Available"),
|
||||
text: text,
|
||||
hide: false
|
||||
};
|
||||
var eventListeners = {};
|
||||
|
||||
if (data.status == "updatePossible" && self.loginState.isAdmin()) {
|
||||
// if user is admin, add action buttons
|
||||
options["confirm"] = {
|
||||
confirm: true,
|
||||
buttons: [{
|
||||
text: gettext("Ignore"),
|
||||
click: function() {
|
||||
self._markNotificationAsSeen(data.information);
|
||||
self._showPopup({
|
||||
text: gettext("You can make this message display again via \"Settings\" > \"SoftwareUpdate\" > \"Check for update now\"")
|
||||
});
|
||||
}
|
||||
}, {
|
||||
text: gettext("Update now"),
|
||||
addClass: "btn-primary",
|
||||
click: self.update
|
||||
}]
|
||||
};
|
||||
options["buttons"] = {
|
||||
closer: false,
|
||||
sticker: false
|
||||
};
|
||||
}
|
||||
|
||||
if (ignoreSeen || !self._hasNotificationBeenSeen(data.information)) {
|
||||
self._showPopup(options, eventListeners);
|
||||
}
|
||||
} else if (data.status == "current" && showIfNothingNew) {
|
||||
self._showPopup({
|
||||
title: gettext("Everything is up-to-date"),
|
||||
hide: false,
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
self.fromCheckResponse(data, ignoreSeen, showIfNothingNew);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -421,6 +433,10 @@ $(function() {
|
|||
self.updateInProgress = false;
|
||||
break;
|
||||
}
|
||||
case "update_versions": {
|
||||
self.performCheck();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (options != undefined) {
|
||||
|
|
@ -432,4 +448,4 @@ $(function() {
|
|||
|
||||
// view model class, parameters for constructor, container to bind to
|
||||
ADDITIONAL_VIEWMODELS.push([SoftwareUpdateViewModel, ["loginStateViewModel", "printerStateViewModel", "settingsViewModel"], document.getElementById("settings_plugin_softwareupdate")]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -210,7 +210,22 @@ class Server():
|
|||
set_preprocessors=set_preprocessors)
|
||||
return dict(settings=plugin_settings)
|
||||
|
||||
def settings_plugin_pre_init(name, implementation):
|
||||
if not isinstance(implementation, octoprint.plugin.SettingsPlugin):
|
||||
return
|
||||
|
||||
settings_version = implementation.get_settings_version()
|
||||
settings_migrator = implementation.on_settings_migrate
|
||||
|
||||
if settings_version is not None and settings_migrator is not None:
|
||||
stored_version = implementation._settings.get_int(["_config_version"])
|
||||
if stored_version is None or stored_version < settings_version:
|
||||
settings_migrator(settings_version, stored_version)
|
||||
implementation._settings.set_int(["_config_version"], settings_version)
|
||||
implementation._settings.save()
|
||||
|
||||
pluginManager.implementation_inject_factories=[octoprint_plugin_inject_factory, settings_plugin_inject_factory]
|
||||
pluginManager.implementation_pre_inits=[settings_plugin_pre_init]
|
||||
pluginManager.initialize_implementations()
|
||||
|
||||
pluginManager.log_all_plugins()
|
||||
|
|
@ -674,6 +689,20 @@ class Server():
|
|||
|
||||
base_folder = settings().getBaseFolder("generated")
|
||||
|
||||
# clean the folder
|
||||
if settings().getBoolean(["devel", "webassets", "clean_on_startup"]):
|
||||
import shutil
|
||||
for entry in ("webassets", ".webassets-cache"):
|
||||
path = os.path.join(base_folder, entry)
|
||||
self._logger.debug("Deleting {path}...".format(**locals()))
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
elif os.path.isfile(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
except:
|
||||
self._logger.exception("Exception while trying to delete {entry} from {base_folder}".format(**locals()))
|
||||
|
||||
AdjustedEnvironment = type(Environment)(Environment.__name__, (Environment,), dict(
|
||||
resolver_class=util.flask.PluginAssetResolver
|
||||
))
|
||||
|
|
@ -754,16 +783,7 @@ class Server():
|
|||
if len(less_app) == 0:
|
||||
less_app = ["empty"]
|
||||
|
||||
js_libs_bundle = Bundle(*js_libs, output="webassets/packed_libs.js")
|
||||
if settings().getBoolean(["devel", "webassets", "minify"]):
|
||||
js_app_bundle = Bundle(*js_app, output="webassets/packed_app.js", filters="rjsmin")
|
||||
else:
|
||||
js_app_bundle = Bundle(*js_app, output="webassets/packed_app.js")
|
||||
|
||||
css_libs_bundle = Bundle(*css_libs, output="webassets/packed_libs.css")
|
||||
css_app_bundle = Bundle(*css_app, output="webassets/packed_app.css")
|
||||
|
||||
from webassets.filter import register_filter
|
||||
from webassets.filter import register_filter, Filter
|
||||
from webassets.filter.cssrewrite.base import PatternRewriter
|
||||
import re
|
||||
class LessImportRewrite(PatternRewriter):
|
||||
|
|
@ -782,7 +802,24 @@ class Server():
|
|||
|
||||
return "{import_with_options}\"{import_url}\";".format(**locals())
|
||||
|
||||
class JsDelimiterBundle(Filter):
|
||||
name = "js_delimiter_bundler"
|
||||
options = {}
|
||||
def input(self, _in, out, **kwargs):
|
||||
out.write(_in.read())
|
||||
out.write("\n;\n")
|
||||
|
||||
register_filter(LessImportRewrite)
|
||||
register_filter(JsDelimiterBundle)
|
||||
|
||||
js_libs_bundle = Bundle(*js_libs, output="webassets/packed_libs.js", filters="js_delimiter_bundler")
|
||||
if settings().getBoolean(["devel", "webassets", "minify"]):
|
||||
js_app_bundle = Bundle(*js_app, output="webassets/packed_app.js", filters="rjsmin, js_delimiter_bundler")
|
||||
else:
|
||||
js_app_bundle = Bundle(*js_app, output="webassets/packed_app.js", filters="js_delimiter_bundler")
|
||||
|
||||
css_libs_bundle = Bundle(*css_libs, output="webassets/packed_libs.css")
|
||||
css_app_bundle = Bundle(*css_app, output="webassets/packed_app.css")
|
||||
|
||||
all_less_bundle = Bundle(*less_app, output="webassets/packed_app.less", filters="less_importrewrite")
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import octoprint.util
|
|||
|
||||
@api.route("/settings", methods=["GET"])
|
||||
def getSettings():
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
s = settings()
|
||||
|
||||
connectionOptions = get_connection_options()
|
||||
|
|
@ -118,7 +120,7 @@ def getSettings():
|
|||
for name in gcode_scripts:
|
||||
data["scripts"]["gcode"][name] = s.loadScript("gcode", name, source=True)
|
||||
|
||||
def process_plugin_result(name, plugin, result):
|
||||
def process_plugin_result(name, result):
|
||||
if result:
|
||||
if not "plugins" in data:
|
||||
data["plugins"] = dict()
|
||||
|
|
@ -126,9 +128,17 @@ def getSettings():
|
|||
del result["__enabled"]
|
||||
data["plugins"][name] = result
|
||||
|
||||
octoprint.plugin.call_plugin(octoprint.plugin.SettingsPlugin,
|
||||
"on_settings_load",
|
||||
callback=process_plugin_result)
|
||||
for plugin in octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SettingsPlugin):
|
||||
try:
|
||||
result = plugin.on_settings_load()
|
||||
process_plugin_result(plugin._identifier, result)
|
||||
except TypeError:
|
||||
logger.warn("Could not load settings for plugin {name} ({version}) since it called super(...)".format(name=plugin._plugin_name, version=plugin._plugin_version))
|
||||
logger.warn("in a way which has issues due to OctoPrint's dynamic reloading after plugin operations.")
|
||||
logger.warn("Please contact the plugin's author and ask to update the plugin to use a direct call like")
|
||||
logger.warn("octoprint.plugin.SettingsPlugin.on_settings_load(self) instead.")
|
||||
except:
|
||||
logger.exception("Could not load settings for plugin {name} ({version})".format(version=plugin._plugin_version, name=plugin._plugin_name))
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
|
@ -137,6 +147,8 @@ def getSettings():
|
|||
@restricted_access
|
||||
@admin_permission.require(403)
|
||||
def setSettings():
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if not "application/json" in request.headers["Content-Type"]:
|
||||
return make_response("Expected content-type JSON", 400)
|
||||
|
||||
|
|
@ -235,8 +247,15 @@ def setSettings():
|
|||
for plugin in octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SettingsPlugin):
|
||||
plugin_id = plugin._identifier
|
||||
if plugin_id in data["plugins"]:
|
||||
plugin.on_settings_save(data["plugins"][plugin_id])
|
||||
|
||||
try:
|
||||
plugin.on_settings_save(data["plugins"][plugin_id])
|
||||
except TypeError:
|
||||
logger.warn("Could not save settings for plugin {name} ({version}) since it called super(...)".format(name=plugin._plugin_name, version=plugin._plugin_version))
|
||||
logger.warn("in a way which has issues due to OctoPrint's dynamic reloading after plugin operations.")
|
||||
logger.warn("Please contact the plugin's author and ask to update the plugin to use a direct call like")
|
||||
logger.warn("octoprint.plugin.SettingsPlugin.on_settings_save(self, data) instead.")
|
||||
except:
|
||||
logger.exception("Could not save settings for plugin {name} ({version})".format(version=plugin._plugin_version, name=plugin._plugin_name))
|
||||
|
||||
if s.save():
|
||||
eventManager().fire(Events.SETTINGS_UPDATED)
|
||||
|
|
|
|||
|
|
@ -251,7 +251,8 @@ default_settings = {
|
|||
},
|
||||
"webassets": {
|
||||
"minify": False,
|
||||
"bundle": True
|
||||
"bundle": True,
|
||||
"clean_on_startup": True
|
||||
},
|
||||
"virtualPrinter": {
|
||||
"enabled": False,
|
||||
|
|
@ -853,7 +854,7 @@ class Settings(object):
|
|||
return results
|
||||
|
||||
def getInt(self, path, config=None, defaults=None, preprocessors=None):
|
||||
value = self.get(path, defaults=defaults, preprocessors=preprocessors)
|
||||
value = self.get(path, config=config, defaults=defaults, preprocessors=preprocessors)
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
|
|
@ -930,14 +931,15 @@ class Settings(object):
|
|||
|
||||
#~~ setter
|
||||
|
||||
def set(self, path, value, force=False, defaults=None, preprocessors=None):
|
||||
def set(self, path, value, force=False, defaults=None, config=None, preprocessors=None):
|
||||
if len(path) == 0:
|
||||
return
|
||||
|
||||
if self._mtime is not None and self.last_modified != self._mtime:
|
||||
self.load()
|
||||
|
||||
config = self._config
|
||||
if config is None:
|
||||
config = self._config
|
||||
if defaults is None:
|
||||
defaults = default_settings
|
||||
if preprocessors is None:
|
||||
|
|
@ -973,9 +975,9 @@ class Settings(object):
|
|||
config[key] = value
|
||||
self._dirty = True
|
||||
|
||||
def setInt(self, path, value, force=False, defaults=None, preprocessors=None):
|
||||
def setInt(self, path, value, force=False, defaults=None, config=None, preprocessors=None):
|
||||
if value is None:
|
||||
self.set(path, None, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
self.set(path, None, config=config, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
@ -984,11 +986,11 @@ class Settings(object):
|
|||
self._logger.warn("Could not convert %r to a valid integer when setting option %r" % (value, path))
|
||||
return
|
||||
|
||||
self.set(path, intValue, force)
|
||||
self.set(path, intValue, config=config, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
|
||||
def setFloat(self, path, value, force=False, defaults=None, preprocessors=None):
|
||||
def setFloat(self, path, value, force=False, defaults=None, config=None, preprocessors=None):
|
||||
if value is None:
|
||||
self.set(path, None, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
self.set(path, None, config=config, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
@ -997,15 +999,15 @@ class Settings(object):
|
|||
self._logger.warn("Could not convert %r to a valid integer when setting option %r" % (value, path))
|
||||
return
|
||||
|
||||
self.set(path, floatValue, force)
|
||||
self.set(path, floatValue, config=config, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
|
||||
def setBoolean(self, path, value, force=False, defaults=None, preprocessors=None):
|
||||
def setBoolean(self, path, value, force=False, defaults=None, config=None, preprocessors=None):
|
||||
if value is None or isinstance(value, bool):
|
||||
self.set(path, value, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
self.set(path, value, config=config, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
elif value.lower() in valid_boolean_trues:
|
||||
self.set(path, True, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
self.set(path, True, config=config, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
else:
|
||||
self.set(path, False, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
self.set(path, False, config=config, force=force, defaults=defaults, preprocessors=preprocessors)
|
||||
|
||||
def setBaseFolder(self, type, path, force=False):
|
||||
if type not in default_settings["folder"].keys():
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class SlicerException(SlicingException):
|
|||
Identifier of the slicer for which the exception was raised.
|
||||
"""
|
||||
def __init__(self, slicer, *args, **kwargs):
|
||||
super(SlicingException, self).__init__(*args, **kwargs)
|
||||
SlicingException.__init__(self, *args, **kwargs)
|
||||
self.slicer = slicer
|
||||
|
||||
class SlicerNotConfigured(SlicerException):
|
||||
|
|
@ -62,7 +62,7 @@ class SlicerNotConfigured(SlicerException):
|
|||
Raised if a slicer is not yet configured but must be configured to proceed.
|
||||
"""
|
||||
def __init__(self, slicer, *args, **kwargs):
|
||||
super(SlicerException, self).__init__(slicer, *args, **kwargs)
|
||||
SlicerException.__init__(self, slicer, *args, **kwargs)
|
||||
self.message = "Slicer not configured: {slicer}".format(slicer=slicer)
|
||||
|
||||
class UnknownSlicer(SlicerException):
|
||||
|
|
@ -70,7 +70,7 @@ class UnknownSlicer(SlicerException):
|
|||
Raised if a slicer is unknown.
|
||||
"""
|
||||
def __init__(self, slicer, *args, **kwargs):
|
||||
super(SlicerException, self).__init__(slicer, *args, **kwargs)
|
||||
SlicerException.__init__(self, slicer, *args, **kwargs)
|
||||
self.message = "No such slicer: {slicer}".format(slicer=slicer)
|
||||
|
||||
class ProfileException(BaseException):
|
||||
|
|
@ -86,7 +86,7 @@ class ProfileException(BaseException):
|
|||
Identifier of the profile for which the exception was raised.
|
||||
"""
|
||||
def __init__(self, slicer, profile, *args, **kwargs):
|
||||
super(BaseException, self).__init__(*args, **kwargs)
|
||||
BaseException.__init__(self, *args, **kwargs)
|
||||
self.slicer = slicer
|
||||
self.profile = profile
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ class UnknownProfile(ProfileException):
|
|||
Raised if a slicing profile does not exist but must exist to proceed.
|
||||
"""
|
||||
def __init__(self, slicer, profile, *args, **kwargs):
|
||||
super(ProfileException, self).__init__(slicer, profile, *args, **kwargs)
|
||||
ProfileException.__init__(self, slicer, profile, *args, **kwargs)
|
||||
self.message = "Profile {profile} for slicer {slicer} does not exist".format(profile=profile, slicer=slicer)
|
||||
|
||||
class ProfileAlreadyExists(ProfileException):
|
||||
|
|
@ -103,5 +103,5 @@ class ProfileAlreadyExists(ProfileException):
|
|||
Raised if a slicing profile already exists and must not be overwritten.
|
||||
"""
|
||||
def __init__(self, slicer, profile, *args, **kwargs):
|
||||
super(ProfileException, self).__init__(slicer, profile, *args, **kwargs)
|
||||
self.message = "Profile {profile} for slicer {slicer} already exists".format(profile=profile, slicer=slicer)
|
||||
ProfileException.__init__(self, slicer, profile, *args, **kwargs)
|
||||
self.message = "Profile {profile} for slicer {slicer} already exists".format(profile=profile, slicer=slicer)
|
||||
|
|
|
|||
Loading…
Reference in a new issue