SWU: Track network connectivity & handle offline scenarios

See also #2011
This commit is contained in:
Gina Häußge 2017-07-19 17:19:36 +02:00
parent 2bc0c4a77f
commit fb8c56be57
16 changed files with 307 additions and 111 deletions

View file

@ -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":

View file

@ -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"]:

View file

@ -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

View file

@ -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}

View file

@ -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;

View file

@ -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;

View file

@ -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>

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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))

View file

@ -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

View file

@ -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")

View file

@ -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))

View file

@ -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))

View file

@ -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