Bundled plugins: Handle connectivity states

See also #2011
This commit is contained in:
Gina Häußge 2017-07-19 18:39:23 +02:00
parent fb8c56be57
commit 0dcbffb9d2
7 changed files with 106 additions and 24 deletions

View file

@ -28,7 +28,8 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
octoprint.plugin.SettingsPlugin,
octoprint.plugin.BlueprintPlugin,
octoprint.plugin.StartupPlugin,
octoprint.plugin.TemplatePlugin):
octoprint.plugin.TemplatePlugin,
octoprint.plugin.EventHandlerPlugin):
def __init__(self):
self._cached_channel_configs = None
@ -210,6 +211,14 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
return NO_CONTENT
##~~ EventHandlerPlugin
def on_event(self, event, payload):
from octoprint.events import Events
if event != Events.CONNECTIVITY_CHANGED or not payload or not payload.get("new", False):
return
self._fetch_all_channels_async()
# Internal Tools
def _mark_read_until(self, channel, until):
@ -266,6 +275,11 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
safe_key = self._slugify(key)
return self._get_channel_configs(force=force).get(safe_key)
def _fetch_all_channels_async(self, force=False):
thread = threading.Thread(target=self._fetch_all_channels, kwargs=dict(force=force))
thread.daemon = True
thread.start()
def _fetch_all_channels(self, force=False):
"""Fetch all channel feeds from cache or network."""
@ -298,7 +312,10 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
if data is None:
# cache not allowed or empty, fetch from network
data = self._get_channel_data_from_network(key, config)
if self._connectivity_checker.online:
data = self._get_channel_data_from_network(key, config)
else:
self._logger.info("Looks like we are offline, can't fetch announcements for channel {} from network".format(key))
return data

View file

@ -317,7 +317,13 @@ $(function() {
self.announcementDialog = $("#plugin_announcements_dialog");
self.announcementDialogContent = $("#plugin_announcements_dialog_content");
self.announcementDialogTabs = $("#plugin_announcements_dialog_tabs");
};
self.onEventConnectivityChanged = function(payload) {
if (!payload || !payload.new) return;
self.retrieveData();
}
}
// view model class, parameters for constructor, container to bind to

View file

@ -36,7 +36,8 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
octoprint.plugin.AssetPlugin,
octoprint.plugin.SettingsPlugin,
octoprint.plugin.StartupPlugin,
octoprint.plugin.BlueprintPlugin):
octoprint.plugin.BlueprintPlugin,
octoprint.plugin.EventHandlerPlugin):
ARCHIVE_EXTENSIONS = (".zip", ".tar.gz", ".tgz", ".tar")
@ -100,13 +101,7 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
self._console_logger.propagate = False
# decouple repository fetching from server startup
def fetch_data():
self._repository_available = self._fetch_repository_from_disk()
self._notices_available = self._fetch_notices_from_disk()
thread = threading.Thread(target=fetch_data)
thread.daemon = True
thread.start()
self._fetch_all_data(async=True)
##~~ SettingsPlugin
@ -197,6 +192,14 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
except Exception as e:
self._logger.warn("Could not remove temporary file {path} again: {message}".format(path=archive.name, message=str(e)))
##~~ EventHandlerPlugin
def on_event(self, event, payload):
from octoprint.events import Events
if event != Events.CONNECTIVITY_CHANGED or not payload or not payload.get("new", False):
return
self._fetch_all_data(async=True)
##~~ SimpleApiPlugin
def get_api_commands(self):
@ -239,7 +242,8 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
additional_args=self._settings.get(["pip_args"]),
python=sys.executable
),
safe_mode=safe_mode)
safe_mode=safe_mode,
online=self._connectivity_checker.online)
def etag():
import hashlib
@ -250,6 +254,7 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
hash.update(str(self._notices_available))
hash.update(repr(self._notices))
hash.update(repr(safe_mode))
hash.update(repr(self._connectivity_checker.online))
return hash.hexdigest()
def condition():
@ -679,6 +684,18 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
elif (plugin.enabled or getattr(plugin, "safe_mode_enabled", False)) and plugin.key not in self._pending_disable:
self._pending_disable.add(plugin.key)
def _fetch_all_data(self, async=False):
def run():
self._repository_available = self._fetch_repository_from_disk()
self._notices_available = self._fetch_notices_from_disk()
if async:
thread = threading.Thread(target=run)
thread.daemon = True
thread.start()
else:
run()
def _fetch_repository_from_disk(self):
repo_data = None
if os.path.isfile(self._repository_cache_path):
@ -696,6 +713,10 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
return self._refresh_repository(repo_data=repo_data)
def _fetch_repository_from_url(self):
if not self._connectivity_checker.online:
self._logger.info("Looks like we are offline, can't fetch repository from network")
return None
repository_url = self._settings.get(["repository"])
try:
r = requests.get(repository_url, timeout=30)
@ -758,13 +779,17 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
import json
with open(self._notices_cache_path) as f:
notice_data = json.load(f)
self._logger.info("Loaded notices from disk, was still valid")
self._logger.info("Loaded notice data from disk, was still valid")
except:
self._logger.exception("Error while loading notices from {}".format(self._notices_cache_path))
return self._refresh_notices(notice_data=notice_data)
def _fetch_notices_from_url(self):
if not self._connectivity_checker.online:
self._logger.info("Looks like we are offline, can't fetch notices from network")
return None
notices_url = self._settings.get(["notices"])
try:
r = requests.get(notices_url, timeout=30)

View file

@ -174,6 +174,7 @@ $(function() {
self.pipPython = ko.observable();
self.safeMode = ko.observable();
self.online = ko.observable();
self.pipUseUserString = ko.pureComputed(function() {
return self.pipUseUser() ? "yes" : "no";
@ -234,7 +235,7 @@ $(function() {
};
self.enableRepoInstall = function(data) {
return self.enableManagement() && self.pipAvailable() && !self.safeMode() && self.isCompatible(data);
return self.enableManagement() && self.pipAvailable() && !self.safeMode() && self.online() && self.isCompatible(data);
};
self.invalidUrl = ko.pureComputed(function() {
@ -244,7 +245,7 @@ $(function() {
self.enableUrlInstall = ko.pureComputed(function() {
var url = self.installUrl();
return self.enableManagement() && self.pipAvailable() && !self.safeMode() && url !== undefined && url.trim() != "" && !self.invalidUrl();
return self.enableManagement() && self.pipAvailable() && !self.safeMode() && self.online() && url !== undefined && url.trim() != "" && !self.invalidUrl();
});
self.invalidArchive = ko.pureComputed(function() {
@ -321,6 +322,7 @@ $(function() {
self._fromPipResponse(data.pip, options);
self.safeMode(data.safe_mode || false);
self.online(data.online !== undefined ? data.online : true);
};
self._fromPluginsResponse = function(data, options) {
@ -1020,6 +1022,11 @@ $(function() {
self._closeAllNotifications();
};
self.onEventConnectivityChanged = function(payload) {
if (!payload || !payload.new) return;
self.requestData({eval_notices: true});
};
self._closeAllNotifications = function() {
if (self.notifications) {
_.each(self.notifications, function(notification) {

View file

@ -20,6 +20,13 @@
{% endtrans %}</div>
{% endmacro %}
{% macro pluginmanager_offline() %}
<div class="alert" data-bind="visible: !online()">{% trans %}
It looks like OctoPrint has currently no connection to the internet. Due to that it's not possible
to install new plugins from the plugin repository or archive URLs.
{% endtrans %}</div>
{% endmacro %}
{{ pluginmanager_printing() }}
{{ pluginmanager_nopip() }}
{{ pluginmanager_safemode() }}
@ -124,6 +131,7 @@
{{ pluginmanager_printing() }}
{{ pluginmanager_nopip() }}
{{ pluginmanager_safemode() }}
{{ pluginmanager_offline() }}
<h4 style="position: relative">
{{ _('... from the <a href="%(url)s" target="_blank">Plugin Repository</a>', url='http://plugins.octoprint.org') }}
<div class="dropdown pull-right">

View file

@ -37,7 +37,8 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
octoprint.plugin.AssetPlugin,
octoprint.plugin.TemplatePlugin,
octoprint.plugin.StartupPlugin,
octoprint.plugin.WizardPlugin):
octoprint.plugin.WizardPlugin,
octoprint.plugin.EventHandlerPlugin):
COMMIT_TRACKING_TYPES = ("github_commit", "bitbucket_commit")
@ -47,8 +48,9 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
self._configured_checks = None
self._refresh_configured_checks = False
self._get_versions_mutex = threading.Lock()
self._get_versions_last = None
self._get_versions_mutex = threading.RLock()
self._get_versions_data = None
self._get_versions_data_ready = threading.Event()
self._version_cache = dict()
self._version_cache_ttl = 0
@ -563,6 +565,17 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
checkout_folder = self._get_octoprint_checkout_folder(checks=checks)
return check and "update_script" in check and not checkout_folder
##~~ EventHandlerPlugin API
def on_event(self, event, payload):
from octoprint.events import Events
if event != Events.CONNECTIVITY_CHANGED or not payload or not payload.get("new", False):
return
thread = threading.Thread(target=self.get_current_versions)
thread.daemon = True
thread.start()
#~~ Updater
def get_current_versions(self, check_targets=None, force=False):
@ -583,6 +596,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
# we don't want to do the same work twice, so let's use a lock
if self._get_versions_mutex.acquire(False):
self._get_versions_data_ready.clear()
try:
futures_to_result = dict()
online = self._connectivity_checker.check_immediately()
@ -657,13 +671,14 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
if self._version_cache_dirty:
self._save_version_cache()
self._get_versions_last = information, update_available, update_possible
self._get_versions_data = information, update_available, update_possible
self._get_versions_data_ready.set()
finally:
self._get_versions_mutex.release()
else: # something's already in progress, let's wait for it to complete and use its result
self._get_versions_mutex.wait()
information, update_available, update_possible = self._get_versions_last
self._get_versions_data_ready.wait()
information, update_available, update_possible = self._get_versions_data
return information, update_available, update_possible
@ -708,14 +723,13 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
information, is_current = version_checker.get_latest(target, check, online=online)
if information is not None and not is_current:
update_available = True
except exceptions.CannotCheckOffline:
update_possible = False
information["needs_online"] = True
except exceptions.UnknownCheckType:
self._logger.warn("Unknown check type %s for %s" % (check["type"], target))
update_possible = False
error = "unknown_check"
except exceptions.CannotCheckOffline:
self._logger.warn("Cannot check %s while we are offline" % (target,))
update_possible = False
information["needs_online"] = True
except exceptions.NetworkError:
self._logger.warn("Could not check %s for updates due to a network error" % target)
update_possible = False

View file

@ -643,6 +643,11 @@ $(function() {
return true;
};
self.onEventConnectivityChanged = function(payload) {
if (!payload || !payload.new) return;
self.performCheck();
};
self.onDataUpdaterReconnect = function() {
if (self.waitingForRestart) {
self.waitingForRestart = false;