SWU: Track network connectivity & handle offline scenarios
See also #2011
This commit is contained in:
parent
2bc0c4a77f
commit
fb8c56be57
16 changed files with 307 additions and 111 deletions
3
setup.py
3
setup.py
|
|
@ -49,7 +49,8 @@ INSTALL_REQUIRES = [
|
|||
"scandir>=1.3,<1.4",
|
||||
"websocket-client>=0.40,<0.41",
|
||||
"python-dateutil>=2.6,<2.7",
|
||||
"wrapt>=1.10.10,<1.11"
|
||||
"wrapt>=1.10.10,<1.11",
|
||||
"futures>=3.1.1,<3.2"
|
||||
]
|
||||
|
||||
if sys.platform == "darwin":
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import time
|
|||
import logging
|
||||
import logging.handlers
|
||||
import hashlib
|
||||
import traceback
|
||||
|
||||
from concurrent import futures
|
||||
|
||||
from . import version_checks, updaters, exceptions, util, cli
|
||||
|
||||
|
|
@ -45,6 +48,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
self._refresh_configured_checks = False
|
||||
|
||||
self._get_versions_mutex = threading.Lock()
|
||||
self._get_versions_last = None
|
||||
|
||||
self._version_cache = dict()
|
||||
self._version_cache_ttl = 0
|
||||
|
|
@ -467,15 +471,6 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
def view():
|
||||
try:
|
||||
information, update_available, update_possible = self.get_current_versions(check_targets=check_targets, force=force)
|
||||
|
||||
# we don't want to transfer python_checker or python_updater values through json - replace with True
|
||||
for key, data in information.items():
|
||||
if "check" in data:
|
||||
if "python_checker" in data["check"]:
|
||||
data["check"]["python_checker"] = True
|
||||
if "python_updater" in data["check"]:
|
||||
data["check"]["python_updater"] = True
|
||||
|
||||
return flask.jsonify(dict(status="updatePossible" if update_available and update_possible else "updateAvailable" if update_available else "current",
|
||||
information=information,
|
||||
timestamp=self._version_cache_timestamp))
|
||||
|
|
@ -502,9 +497,11 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
hash.update(repr(data["information"]))
|
||||
hash.update(str(data["available"]))
|
||||
hash.update(str(data["possible"]))
|
||||
hash.update(str(data.get("online", None)))
|
||||
|
||||
hash.update(",".join(targets))
|
||||
hash.update(str(self._version_cache_timestamp))
|
||||
hash.update(str(self._connectivity_checker.online))
|
||||
return hash.hexdigest()
|
||||
|
||||
def condition():
|
||||
|
|
@ -585,53 +582,89 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
information = dict()
|
||||
|
||||
# we don't want to do the same work twice, so let's use a lock
|
||||
with self._get_versions_mutex:
|
||||
for target, check in checks.items():
|
||||
if not target in check_targets:
|
||||
continue
|
||||
if self._get_versions_mutex.acquire(False):
|
||||
try:
|
||||
futures_to_result = dict()
|
||||
online = self._connectivity_checker.check_immediately()
|
||||
self._logger.debug("Looks like we are {}".format("online" if online else "offline"))
|
||||
|
||||
if not check:
|
||||
continue
|
||||
with futures.ThreadPoolExecutor(max_workers=5) as executor:
|
||||
for target, check in checks.items():
|
||||
if not target in check_targets:
|
||||
continue
|
||||
|
||||
try:
|
||||
populated_check = self._populated_check(target, check)
|
||||
target_information, target_update_available, target_update_possible = self._get_current_version(target, populated_check, force=force)
|
||||
if target_information is None:
|
||||
target_information = dict()
|
||||
except exceptions.UnknownCheckType:
|
||||
self._logger.warn("Unknown update check type for target {}: {}".format(target, check.get("type", "<n/a>")))
|
||||
continue
|
||||
if not check:
|
||||
continue
|
||||
|
||||
target_information = dict_merge(dict(local=dict(name="unknown", value="unknown"), remote=dict(name="unknown", value="unknown", release_notes=None)), target_information)
|
||||
try:
|
||||
populated_check = self._populated_check(target, check)
|
||||
future = executor.submit(self._get_current_version, target, populated_check, force=force)
|
||||
futures_to_result[future] = (target, populated_check)
|
||||
except exceptions.UnknownCheckType:
|
||||
self._logger.warn("Unknown update check type for target {}: {}".format(target,
|
||||
check.get("type",
|
||||
"<n/a>")))
|
||||
continue
|
||||
except:
|
||||
self._logger.exception("Could not check {} for updates".format(target))
|
||||
continue
|
||||
|
||||
update_available = update_available or target_update_available
|
||||
update_possible = update_possible or (target_update_possible and target_update_available)
|
||||
for future in futures.as_completed(futures_to_result):
|
||||
|
||||
local_name = target_information["local"]["name"]
|
||||
local_value = target_information["local"]["value"]
|
||||
target, populated_check = futures_to_result[future]
|
||||
if future.exception() is not None:
|
||||
self._logger.error("Could not check {} for updates, error: {!r}".format(target,
|
||||
future.exception()))
|
||||
continue
|
||||
|
||||
release_notes = None
|
||||
if target_information and target_information["remote"] and target_information["remote"]["value"]:
|
||||
if "release_notes" in populated_check and populated_check["release_notes"]:
|
||||
release_notes = populated_check["release_notes"]
|
||||
elif "release_notes" in target_information["remote"]:
|
||||
release_notes = target_information["remote"]["release_notes"]
|
||||
target_information, target_update_available, target_update_possible, target_online, target_error = future.result()
|
||||
|
||||
if release_notes:
|
||||
release_notes = release_notes.format(octoprint_version=VERSION,
|
||||
target_name=target_information["remote"]["name"],
|
||||
target_version=target_information["remote"]["value"])
|
||||
target_information = dict_merge(dict(local=dict(name="?", value="?"),
|
||||
remote=dict(name="?", value="?",
|
||||
release_notes=None),
|
||||
needs_online=True), target_information)
|
||||
|
||||
information[target] = dict(updateAvailable=target_update_available,
|
||||
updatePossible=target_update_possible,
|
||||
information=target_information,
|
||||
displayName=populated_check["displayName"],
|
||||
displayVersion=populated_check["displayVersion"].format(octoprint_version=VERSION, local_name=local_name, local_value=local_value),
|
||||
check=populated_check,
|
||||
releaseNotes=release_notes)
|
||||
update_available = update_available or target_update_available
|
||||
update_possible = update_possible or (target_update_possible and target_update_available)
|
||||
|
||||
local_name = target_information["local"]["name"]
|
||||
local_value = target_information["local"]["value"]
|
||||
|
||||
release_notes = None
|
||||
if target_information and target_information["remote"] and target_information["remote"][
|
||||
"value"]:
|
||||
if "release_notes" in populated_check and populated_check["release_notes"]:
|
||||
release_notes = populated_check["release_notes"]
|
||||
elif "release_notes" in target_information["remote"]:
|
||||
release_notes = target_information["remote"]["release_notes"]
|
||||
|
||||
if release_notes:
|
||||
release_notes = release_notes.format(octoprint_version=VERSION,
|
||||
target_name=target_information["remote"]["name"],
|
||||
target_version=target_information["remote"]["value"])
|
||||
|
||||
information[target] = dict(updateAvailable=target_update_available,
|
||||
updatePossible=target_update_possible,
|
||||
information=target_information,
|
||||
displayName=populated_check["displayName"],
|
||||
displayVersion=populated_check["displayVersion"].format(octoprint_version=VERSION,
|
||||
local_name=local_name,
|
||||
local_value=local_value),
|
||||
releaseNotes=release_notes,
|
||||
online=target_online,
|
||||
error=target_error)
|
||||
|
||||
if self._version_cache_dirty:
|
||||
self._save_version_cache()
|
||||
|
||||
self._get_versions_last = information, update_available, update_possible
|
||||
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
|
||||
|
||||
if self._version_cache_dirty:
|
||||
self._save_version_cache()
|
||||
return information, update_available, update_possible
|
||||
|
||||
def _get_check_hash(self, check):
|
||||
|
|
@ -650,36 +683,51 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
hash.update(dict_to_sorted_repr(check))
|
||||
return hash.hexdigest()
|
||||
|
||||
def _get_current_version(self, target, check, force=False):
|
||||
def _get_current_version(self, target, check, force=False, online=None):
|
||||
"""
|
||||
Determines the current version information for one target based on its check configuration.
|
||||
"""
|
||||
|
||||
current_hash = self._get_check_hash(check)
|
||||
if online is None:
|
||||
online = self._connectivity_checker.online
|
||||
if target in self._version_cache and not force:
|
||||
data = self._version_cache[target]
|
||||
if data["hash"] == current_hash and data["timestamp"] + self._version_cache_ttl >= time.time() > data["timestamp"]:
|
||||
if data["hash"] == current_hash \
|
||||
and data["timestamp"] + self._version_cache_ttl >= time.time() > data["timestamp"] \
|
||||
and data.get("online", None) == online:
|
||||
# we also check that timestamp < now to not get confused too much by clock changes
|
||||
return data["information"], data["available"], data["possible"]
|
||||
return data["information"], data["available"], data["possible"], data["online"], data.get("error", None)
|
||||
|
||||
information = dict()
|
||||
update_available = False
|
||||
error = None
|
||||
|
||||
try:
|
||||
version_checker = self._get_version_checker(target, check)
|
||||
information, is_current = version_checker.get_latest(target, check)
|
||||
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.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
|
||||
error = "network"
|
||||
except:
|
||||
self._logger.exception("Could not check %s for updates" % target)
|
||||
update_possible = False
|
||||
error = "unknown"
|
||||
else:
|
||||
try:
|
||||
updater = self._get_updater(target, check)
|
||||
update_possible = updater.can_perform_update(target, check)
|
||||
update_possible = updater.can_perform_update(target, check, online=online)
|
||||
except:
|
||||
update_possible = False
|
||||
|
||||
|
|
@ -687,9 +735,11 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
hash=current_hash,
|
||||
information=information,
|
||||
available=update_available,
|
||||
possible=update_possible)
|
||||
possible=update_possible,
|
||||
online=online,
|
||||
error=error)
|
||||
self._version_cache_dirty = True
|
||||
return information, update_available, update_possible
|
||||
return information, update_available, update_possible, online, error
|
||||
|
||||
def perform_updates(self, check_targets=None, force=False):
|
||||
"""
|
||||
|
|
@ -801,7 +851,9 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
self._send_client_message("success", dict(results=target_results))
|
||||
|
||||
def _perform_update(self, target, check, force):
|
||||
information, update_available, update_possible = self._get_current_version(target, check)
|
||||
online = self._connectivity_checker.online
|
||||
|
||||
information, update_available, update_possible, _, _ = self._get_current_version(target, check, online=online)
|
||||
|
||||
if not update_available and not force:
|
||||
return False, None
|
||||
|
|
@ -824,7 +876,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
if updater is None:
|
||||
raise exceptions.UnknownUpdateType()
|
||||
|
||||
update_result = updater.perform_update(target, populated_check, target_version, log_cb=self._log)
|
||||
update_result = updater.perform_update(target, populated_check, target_version, log_cb=self._log, online=online)
|
||||
target_result = ("success", update_result)
|
||||
self._logger.info("Update of %s to %s successful!" % (target, target_version))
|
||||
|
||||
|
|
@ -833,6 +885,10 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
self._send_client_message("update_failed", dict(target=target, version=target_version, name=populated_check["displayName"], reason="Unknown update type"))
|
||||
return False, None
|
||||
|
||||
except exceptions.CannotUpdateOffline:
|
||||
self._logger.warn("Update of %s can not be performed, it's not marked as 'offline' capable but we are apparently offline right now" % target)
|
||||
self._send_client_message("update_failed", dict(target=target, version=target_version, name=populated_check["displayName"], reason="No internet connection"))
|
||||
|
||||
except Exception as e:
|
||||
self._logger.exception("Update of %s can not be performed" % target)
|
||||
if not "ignorable" in populated_check or not populated_check["ignorable"]:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,20 @@ class UnknownUpdateType(Exception):
|
|||
class UnknownCheckType(Exception):
|
||||
pass
|
||||
|
||||
class NetworkError(Exception):
|
||||
def __init__(self, message=None, cause=None):
|
||||
Exception.__init__(self)
|
||||
self.message = message
|
||||
self.cause = cause
|
||||
|
||||
def __str__(self):
|
||||
if self.message is not None:
|
||||
return self.message
|
||||
elif self.cause is not None:
|
||||
return "NetworkError caused by {}".format(self.cause)
|
||||
else:
|
||||
return "NetworkError"
|
||||
|
||||
class UpdateError(Exception):
|
||||
def __init__(self, message, data):
|
||||
self.message = message
|
||||
|
|
@ -35,3 +49,8 @@ class RestartFailed(Exception):
|
|||
class ConfigurationInvalid(Exception):
|
||||
pass
|
||||
|
||||
class CannotCheckOffline(Exception):
|
||||
pass
|
||||
|
||||
class CannotUpdateOffline(Exception):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
td.settings_plugin_softwareupdate_column_update{width:16px}#settings_plugin_softwareupdate_workingdialog_output{font-size:.8em}#settings_plugin_softwareupdate_workingdialog_output .message{font-weight:700}#settings_plugin_softwareupdate_workingdialog_output .separator{font-weight:700;color:#666}#settings_plugin_softwareupdate_workingdialog_output .stdout{color:#333}#settings_plugin_softwareupdate_workingdialog_output .stderr{color:#900}#settings_plugin_softwareupdate_workingdialog_output .call{color:#009}#settings_plugin_softwareupdate_workingdialog_output .message_error{font-weight:700;color:#900}.softwareupdate_notification ul{margin:10px 0 10px 25px}.softwareupdate_notification ul .name{text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block}
|
||||
td.settings_plugin_softwareupdate_column_update{width:16px}td.settings_plugin_softwareupdate_column_information .line{display:block}#settings_plugin_softwareupdate_workingdialog_output{font-size:.8em}#settings_plugin_softwareupdate_workingdialog_output .message{font-weight:700}#settings_plugin_softwareupdate_workingdialog_output .separator{font-weight:700;color:#666}#settings_plugin_softwareupdate_workingdialog_output .stdout{color:#333}#settings_plugin_softwareupdate_workingdialog_output .stderr{color:#900}#settings_plugin_softwareupdate_workingdialog_output .call{color:#009}#settings_plugin_softwareupdate_workingdialog_output .message_error{font-weight:700;color:#900}.softwareupdate_notification ul{margin:10px 0 10px 25px}.softwareupdate_notification ul .name{text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block}
|
||||
|
|
@ -417,6 +417,42 @@ $(function() {
|
|||
});
|
||||
};
|
||||
|
||||
self.iconTitleForEntry = function(data) {
|
||||
if (data.updatePossible) {
|
||||
return "";
|
||||
} else if (!data.online && data.information && data.information.needs_online) {
|
||||
return gettext("No internet connection");
|
||||
} else if (data.error) {
|
||||
return self.errorTextForEntry(data);
|
||||
} else {
|
||||
return gettext("Update not possible");
|
||||
}
|
||||
};
|
||||
|
||||
self.errorTextForEntry = function(data) {
|
||||
if (!data.error) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (data.error) {
|
||||
case "unknown_check": {
|
||||
return gettext("Unknown update check, configuration ok?");
|
||||
}
|
||||
case "needs_online": {
|
||||
return gettext("Cannot check for update, need online connection");
|
||||
}
|
||||
case "network": {
|
||||
return gettext("Network error while checking for update");
|
||||
}
|
||||
case "unknown": {
|
||||
return gettext("Unknown error while checking for update, please check the logs");
|
||||
}
|
||||
default: {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self._markNotificationAsSeen = function(data) {
|
||||
if (!Modernizr.localstorage)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ td.settings_plugin_softwareupdate_column_update {
|
|||
width: 16px;
|
||||
}
|
||||
|
||||
td.settings_plugin_softwareupdate_column_information {
|
||||
.line {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
#settings_plugin_softwareupdate_workingdialog_output {
|
||||
font-size: 0.8em;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,11 +29,12 @@
|
|||
<span data-bind="invisible: !updateAvailable"><i class="fa fa-bell-o" title="{{ _('Update available') }}"></i></span>
|
||||
</td>
|
||||
<td class="settings_plugin_softwareupdate_column_information">
|
||||
<strong data-bind="text: displayName"></strong>: <span data-bind="text: displayVersion"></span> <span data-bind="invisible: updatePossible"><i class="fa fa-exclamation-triangle" title="{{ _('Update not possible, configuration ok?') }}"></i></span><br>
|
||||
<strong data-bind="text: displayName"></strong>: <span data-bind="text: displayVersion"></span> <span data-bind="invisible: updatePossible"><i class="fa fa-exclamation-triangle" data-bind="css: {'fa-unlink': !online && information && information.needs_online, 'fa-exclamation-triangle': !(!online && information && information.needs_online)}, attr: {title: $root.iconTitleForEntry($data)}"></i></span><br>
|
||||
<small class="muted">
|
||||
{{ _('Installed:') }} <span data-bind="text: information.local.name"></span><br>
|
||||
{{ _('Available:') }} <span data-bind="text: information.remote.name"></span><br>
|
||||
<span data-bind="visible: releaseNotes">{{ _('Release Notes:') }} <a data-bind="attr: {href: releaseNotes}, text: releaseNotes" target="_blank" rel="noreferrer noopener"></a></span>
|
||||
<span class="line">{{ _('Installed:') }} <span data-bind="text: information.local.name"></span></span>
|
||||
<span class="line">{{ _('Available:') }} <span data-bind="text: information.remote.name"></span></span>
|
||||
<span class="line" data-bind="visible: releaseNotes">{{ _('Release Notes:') }} <a data-bind="attr: {href: releaseNotes}, text: releaseNotes" target="_blank" rel="noreferrer noopener"></a></span>
|
||||
<span class="line text-error" data-bind="visible: error, text: $root.errorTextForEntry($data)"></span>
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ console_logger = logging.getLogger("octoprint.plugins.softwareupdate.updaters.pi
|
|||
_pip_callers = dict()
|
||||
_pip_version_dependency_links = pkg_resources.parse_version("1.5")
|
||||
|
||||
def can_perform_update(target, check):
|
||||
def can_perform_update(target, check, online=True):
|
||||
pip_caller = _get_pip_caller(command=check["pip_command"] if "pip_command" in check else None)
|
||||
return "pip" in check and pip_caller is not None and pip_caller.available
|
||||
return "pip" in check and pip_caller is not None and pip_caller.available and (online or check.get("offline", False))
|
||||
|
||||
def _get_pip_caller(command=None):
|
||||
key = command
|
||||
|
|
@ -35,11 +35,14 @@ def _get_pip_caller(command=None):
|
|||
|
||||
return _pip_callers[key]
|
||||
|
||||
def perform_update(target, check, target_version, log_cb=None):
|
||||
def perform_update(target, check, target_version, log_cb=None, online=True):
|
||||
pip_command = None
|
||||
if "pip_command" in check:
|
||||
pip_command = check["pip_command"]
|
||||
|
||||
if not online and not check.get("offline", False):
|
||||
raise exceptions.CannotUpdateOffline()
|
||||
|
||||
pip_caller = _get_pip_caller(command=pip_command)
|
||||
if pip_caller is None:
|
||||
raise exceptions.UpdateError("Can't run pip", None)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,24 @@ __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agp
|
|||
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
||||
|
||||
|
||||
def can_perform_update(target, check):
|
||||
return "python_updater" in check and check["python_updater"] is not None and hasattr(check["python_updater"], "perform_update")
|
||||
def can_perform_update(target, check, online=True):
|
||||
return "python_updater" in check and check["python_updater"] is not None and hasattr(check["python_updater"], "perform_update") and (online or check.get("offline", False))
|
||||
|
||||
|
||||
def perform_update(target, check, target_version, log_cb=None):
|
||||
return check["python_updater"].perform_update(target, check, target_version, log_cb=log_cb)
|
||||
def perform_update(target, check, target_version, log_cb=None, online=True):
|
||||
from ..exceptions import CannotUpdateOffline
|
||||
|
||||
if not online and not check("offline", False):
|
||||
raise CannotUpdateOffline()
|
||||
|
||||
try:
|
||||
return check["python_updater"].perform_update(target, check, target_version, log_cb=log_cb, online=online)
|
||||
except:
|
||||
import inspect
|
||||
args, _, _, _ = inspect.getargspec(check["python_updater"].perform_update)
|
||||
if "online" not in args:
|
||||
# old python_updater footprint, simply leave out the online parameter
|
||||
return check["python_updater"].perform_update(target, check, target_version, log_cb=log_cb)
|
||||
|
||||
# some other error, raise again
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
|
|||
import sys
|
||||
import logging
|
||||
|
||||
from ..exceptions import ConfigurationInvalid, UpdateError
|
||||
from ..exceptions import ConfigurationInvalid, UpdateError, CannotUpdateOffline
|
||||
|
||||
from octoprint.util.commandline import CommandlineCaller, CommandlineError
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ def _get_caller(log_cb=None):
|
|||
return caller
|
||||
|
||||
|
||||
def can_perform_update(target, check):
|
||||
def can_perform_update(target, check, online=True):
|
||||
import os
|
||||
script_configured = bool("update_script" in check and check["update_script"])
|
||||
|
||||
|
|
@ -47,12 +47,15 @@ def can_perform_update(target, check):
|
|||
folder = check["checkout_folder"]
|
||||
folder_configured = bool(folder and os.path.isdir(folder))
|
||||
|
||||
return script_configured and folder_configured
|
||||
return script_configured and folder_configured and (online or check.get("offline", False))
|
||||
|
||||
|
||||
def perform_update(target, check, target_version, log_cb=None):
|
||||
def perform_update(target, check, target_version, log_cb=None, online=True):
|
||||
logger = logging.getLogger("octoprint.plugins.softwareupdate.updaters.update_script")
|
||||
|
||||
if not online and not check("offline", False):
|
||||
raise CannotUpdateOffline()
|
||||
|
||||
if not can_perform_update(target, check):
|
||||
raise ConfigurationInvalid("checkout_folder and update_folder are missing for update target %s, one is needed" % target)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,12 +14,18 @@ logger = logging.getLogger("octoprint.plugins.softwareupdate.version_checks.bitb
|
|||
|
||||
|
||||
def _get_latest_commit(user, repo, branch, api_user=None, api_password=None):
|
||||
from ..exceptions import NetworkError
|
||||
|
||||
url = BRANCH_HEAD_URL.format(user=user, repo=repo, branch=branch)
|
||||
headers = {}
|
||||
if api_user is not None and api_password is not None:
|
||||
auth_value = base64.b64encode(b"{user}:{pw}".format(user=api_user, pw=api_password))
|
||||
headers["authorization"] = "Basic {}".format(auth_value)
|
||||
r = requests.get(url, headers=headers)
|
||||
|
||||
try:
|
||||
r = requests.get(url, headers=headers, timeout=(3.05, 30))
|
||||
except requests.ConnectionError as exc:
|
||||
raise NetworkError(cause=exc)
|
||||
|
||||
if not r.status_code == requests.codes.ok:
|
||||
return None
|
||||
|
|
@ -31,7 +37,7 @@ def _get_latest_commit(user, repo, branch, api_user=None, api_password=None):
|
|||
return reference["hash"]
|
||||
|
||||
|
||||
def get_latest(target, check):
|
||||
def get_latest(target, check, online=True):
|
||||
from ..exceptions import ConfigurationInvalid
|
||||
|
||||
if "user" not in check or "repo" not in check:
|
||||
|
|
@ -46,12 +52,18 @@ def get_latest(target, check):
|
|||
|
||||
current = check.get("current")
|
||||
|
||||
remote_commit = _get_latest_commit(check["user"], check["repo"], branch, api_user, api_password)
|
||||
|
||||
information = dict(
|
||||
local=dict(name="Commit {commit}".format(commit=current if current is not None else "unknown"), value=current),
|
||||
remote=dict(name="Commit {commit}".format(commit=remote_commit if remote_commit is not None else "unknown"), value=remote_commit)
|
||||
remote=dict(name="?", value="?"),
|
||||
needs_online=not check.get("offline", False)
|
||||
)
|
||||
if not online and information["needs_online"]:
|
||||
return information, True
|
||||
|
||||
remote_commit = _get_latest_commit(check["user"], check["repo"], branch, api_user, api_password)
|
||||
remote_name = "Commit {commit}".format(commit=remote_commit) if remote_commit is not None else "-"
|
||||
|
||||
information["remote"] = dict(name=remote_name, value=remote_commit)
|
||||
is_current = (current is not None and current == remote_commit) or remote_commit is None
|
||||
|
||||
logger.debug("Target: %s, local: %s, remote: %s" % (target, current, remote_commit))
|
||||
|
|
|
|||
|
|
@ -8,14 +8,17 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
|
|||
|
||||
import logging
|
||||
|
||||
from ..exceptions import ConfigurationInvalid
|
||||
from ..exceptions import ConfigurationInvalid, CannotCheckOffline
|
||||
from ..util import execute
|
||||
|
||||
def get_latest(target, check):
|
||||
def get_latest(target, check, online=True):
|
||||
command = check.get("command")
|
||||
if command is None:
|
||||
raise ConfigurationInvalid("Update configuration for {} of type commandline needs command set and not None".format(target))
|
||||
|
||||
if not online and not check.get("offline", False):
|
||||
raise CannotCheckOffline("{} isn't marked as 'offline' capable, but we are apparently offline right now".format(target))
|
||||
|
||||
returncode, stdout, stderr = execute(command, evaluate_returncode=False)
|
||||
|
||||
# We expect command line check commands to
|
||||
|
|
|
|||
|
|
@ -47,38 +47,40 @@ def _git(args, cwd, hide_stderr=False):
|
|||
return p.returncode, stdout
|
||||
|
||||
|
||||
def get_latest(target, check):
|
||||
def get_latest(target, check, online=True):
|
||||
checkout_folder = check.get("checkout_folder")
|
||||
if checkout_folder is None:
|
||||
raise ConfigurationInvalid("Update configuration for {} of type git_commit needs checkout_folder set and not None".format(target))
|
||||
|
||||
returncode, _ = _git(["fetch"], checkout_folder)
|
||||
if returncode != 0:
|
||||
return None, True
|
||||
|
||||
returncode, local_commit = _git(["rev-parse", "@{0}"], checkout_folder)
|
||||
if returncode != 0:
|
||||
return None, True
|
||||
|
||||
information = dict(
|
||||
local=dict(name="Commit %s" % local_commit, value=local_commit),
|
||||
remote=dict(name="?", value="?"),
|
||||
needs_online=not check.get("offline", False)
|
||||
)
|
||||
if not online and information["needs_online"]:
|
||||
return information, True
|
||||
|
||||
returncode, _ = _git(["fetch"], checkout_folder)
|
||||
if returncode != 0:
|
||||
return information, True
|
||||
|
||||
returncode, remote_commit = _git(["rev-parse", "@{u}"], checkout_folder)
|
||||
if returncode != 0:
|
||||
return None, True
|
||||
return information, True
|
||||
|
||||
returncode, base = _git(["merge-base", "@{0}", "@{u}"], checkout_folder)
|
||||
if returncode != 0:
|
||||
return None, True
|
||||
return information, True
|
||||
|
||||
if local_commit == remote_commit or remote_commit == base:
|
||||
information = dict(
|
||||
local=dict(name="Commit %s" % local_commit, value=local_commit),
|
||||
remote=dict(name="Commit %s" % local_commit, value=local_commit)
|
||||
)
|
||||
information["remote"] = dict(name="Commit %s" % local_commit, value=local_commit)
|
||||
is_current = True
|
||||
else:
|
||||
information = dict(
|
||||
local=dict(name="Commit %s" % local_commit, value=local_commit),
|
||||
remote=dict(name="Commit %s" % remote_commit, value=remote_commit)
|
||||
)
|
||||
information["remote"] = dict(name="Commit %s" % remote_commit, value=remote_commit)
|
||||
is_current = local_commit == remote_commit
|
||||
|
||||
logger = logging.getLogger("octoprint.plugins.softwareupdate.version_checks.git_commit")
|
||||
|
|
|
|||
|
|
@ -13,7 +13,12 @@ BRANCH_HEAD_URL = "https://api.github.com/repos/{user}/{repo}/git/refs/heads/{br
|
|||
logger = logging.getLogger("octoprint.plugins.softwareupdate.version_checks.github_commit")
|
||||
|
||||
def _get_latest_commit(user, repo, branch):
|
||||
r = requests.get(BRANCH_HEAD_URL.format(user=user, repo=repo, branch=branch), timeout=30)
|
||||
from ..exceptions import NetworkError
|
||||
|
||||
try:
|
||||
r = requests.get(BRANCH_HEAD_URL.format(user=user, repo=repo, branch=branch), timeout=(3.05, 30))
|
||||
except requests.ConnectionError as exc:
|
||||
raise NetworkError(cause=exc)
|
||||
|
||||
from . import log_github_ratelimit
|
||||
log_github_ratelimit(logger, r)
|
||||
|
|
@ -28,7 +33,7 @@ def _get_latest_commit(user, repo, branch):
|
|||
return reference["object"]["sha"]
|
||||
|
||||
|
||||
def get_latest(target, check):
|
||||
def get_latest(target, check, online=True):
|
||||
from ..exceptions import ConfigurationInvalid
|
||||
|
||||
user = check.get("user")
|
||||
|
|
@ -43,12 +48,16 @@ def get_latest(target, check):
|
|||
|
||||
current = check.get("current")
|
||||
|
||||
remote_commit = _get_latest_commit(check["user"], check["repo"], branch)
|
||||
information = dict(local=dict(name="Commit {commit}".format(commit=current if current is not None else "?"), value=current),
|
||||
remote=dict(name="?", value="?"),
|
||||
needs_online=not check.get("offline", False))
|
||||
if not online and information["needs_online"]:
|
||||
return information, True
|
||||
|
||||
information = dict(
|
||||
local=dict(name="Commit {commit}".format(commit=current if current is not None else "unknown"), value=current),
|
||||
remote=dict(name="Commit {commit}".format(commit=remote_commit if remote_commit is not None else "unknown"), value=remote_commit)
|
||||
)
|
||||
remote_commit = _get_latest_commit(check["user"], check["repo"], branch)
|
||||
remote_name = "Commit {commit}".format(commit=remote_commit) if remote_commit is not None else "-"
|
||||
|
||||
information["remote"] = dict(name=remote_name, value=remote_commit)
|
||||
is_current = (current is not None and current == remote_commit) or remote_commit is None
|
||||
|
||||
logger.debug("Target: %s, local: %s, remote: %s" % (target, current, remote_commit))
|
||||
|
|
|
|||
|
|
@ -96,8 +96,14 @@ def _get_latest_release(user, repo, compare_type,
|
|||
include_prerelease=False,
|
||||
commitish=None,
|
||||
force_base=True):
|
||||
from ..exceptions import NetworkError
|
||||
|
||||
nothing = None, None, None
|
||||
r = requests.get(RELEASE_URL.format(user=user, repo=repo), timeout=30)
|
||||
|
||||
try:
|
||||
r = requests.get(RELEASE_URL.format(user=user, repo=repo), timeout=(3.05, 30))
|
||||
except requests.ConnectionError as exc:
|
||||
raise NetworkError(cause=exc)
|
||||
|
||||
from . import log_github_ratelimit
|
||||
log_github_ratelimit(logger, r)
|
||||
|
|
@ -258,7 +264,7 @@ def _is_current(release_information, compare_type, custom=None, force_base=True)
|
|||
return True
|
||||
|
||||
|
||||
def get_latest(target, check, custom_compare=None):
|
||||
def get_latest(target, check, custom_compare=None, online=True):
|
||||
from ..exceptions import ConfigurationInvalid
|
||||
|
||||
user = check.get("user", None)
|
||||
|
|
@ -267,6 +273,14 @@ def get_latest(target, check, custom_compare=None):
|
|||
if user is None or repo is None or current is None:
|
||||
raise ConfigurationInvalid("Update configuration for {} of type github_release needs all of user, repo and current set and not None".format(target))
|
||||
|
||||
information =dict(
|
||||
local=dict(name=current, value=current),
|
||||
remote=dict(name="?", value="?", release_notes=None),
|
||||
needs_online=not check.get("offline", False)
|
||||
)
|
||||
if not online and information["needs_online"]:
|
||||
return information, True
|
||||
|
||||
include_prerelease = check.get("prerelease", False)
|
||||
prerelease_channel = check.get("prerelease_channel", None)
|
||||
|
||||
|
|
@ -290,10 +304,13 @@ def get_latest(target, check, custom_compare=None):
|
|||
commitish=commitish,
|
||||
force_base=force_base)
|
||||
|
||||
information =dict(
|
||||
local=dict(name=current, value=current),
|
||||
remote=dict(name=remote_name, value=remote_tag, release_notes=release_notes)
|
||||
)
|
||||
if remote_name is None:
|
||||
if remote_tag is not None:
|
||||
remote_name = remote_tag
|
||||
else:
|
||||
remote_name = "-"
|
||||
|
||||
information["remote"] = dict(name=remote_name, value=remote_tag, release_notes=release_notes)
|
||||
|
||||
logger.debug("Target: %s, local: %s, remote: %s" % (target, current, remote_tag))
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,24 @@ __author__ = "Gina Häußge <osd@foosel.net>"
|
|||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
||||
|
||||
from ..exceptions import ConfigurationInvalid
|
||||
from ..exceptions import ConfigurationInvalid, CannotCheckOffline
|
||||
|
||||
def get_latest(target, check, full_data=False):
|
||||
def get_latest(target, check, full_data=False, online=True):
|
||||
python_checker = check.get("python_checker")
|
||||
if python_checker is None or not hasattr(python_checker, "get_latest"):
|
||||
raise ConfigurationInvalid("Update configuration for {} of type python_checker needs python_checker defined and have an attribute \"get_latest\"".format(target))
|
||||
|
||||
return check["python_checker"].get_latest(target, check, full_data=full_data)
|
||||
if not online and not check.get("offline", False):
|
||||
raise CannotCheckOffline("{} isn't marked as 'offline' capable, but we are apparently offline right now".format(target))
|
||||
|
||||
try:
|
||||
return check["python_checker"].get_latest(target, check, full_data=full_data, online=online)
|
||||
except:
|
||||
import inspect
|
||||
args, _, _, _ = inspect.getargspec(check["python_checker"].get_latest)
|
||||
if "online" not in args:
|
||||
# old python_checker footprint, simply leave out the online parameter
|
||||
return check["python_checker"].get_latest(target, check, full_data=full_data)
|
||||
|
||||
# some other error, raise again
|
||||
raise
|
||||
|
|
|
|||
Loading…
Reference in a new issue