Extensions: improve checks for packages to upgrade
- All remote repositories are included in the check for outdated packages, the check-for-updates setting is only used to sync with the remote before checking for outdated packages. - Defer checking for repositories to notify until after Blender has opened since checking for installed packages & outdated repositories could add some noticeable overhead.
This commit is contained in:
@@ -212,11 +212,11 @@ def repos_to_notify():
|
||||
|
||||
prefs = bpy.context.preferences
|
||||
extension_repos = prefs.extensions.repos
|
||||
|
||||
repos_remote = []
|
||||
for repo_item in extension_repos:
|
||||
if not repo_item.enabled:
|
||||
continue
|
||||
if not repo_item.use_sync_on_startup:
|
||||
continue
|
||||
if not repo_item.use_remote_url:
|
||||
continue
|
||||
remote_url = repo_item.remote_url
|
||||
@@ -247,8 +247,20 @@ def repos_to_notify():
|
||||
if repo_is_empty:
|
||||
continue
|
||||
|
||||
# NOTE: offline checks are handled by the notification (not here).
|
||||
repos_notify.append(
|
||||
repos_remote.append(repo_item)
|
||||
|
||||
# Update all repos together or none, to avoid bothering users
|
||||
# multiple times in a day.
|
||||
do_online_sync = False
|
||||
for repo_item in repos_remote:
|
||||
if not repo_item.use_sync_on_startup:
|
||||
continue
|
||||
if repo_index_outdated(repo_item.directory):
|
||||
do_online_sync = True
|
||||
break
|
||||
|
||||
for repo_item in repos_remote:
|
||||
repos_notify.append((
|
||||
bl_extension_ops.RepoItem(
|
||||
name=repo_item.name,
|
||||
directory=repo_directory,
|
||||
@@ -257,14 +269,10 @@ def repos_to_notify():
|
||||
use_cache=repo_item.use_cache,
|
||||
access_token=repo_item.access_token if repo_item.use_access_token else "",
|
||||
),
|
||||
)
|
||||
repo_item.use_sync_on_startup and do_online_sync,
|
||||
))
|
||||
|
||||
# Update all repos together or none, to avoid bothering users
|
||||
# multiple times in a day.
|
||||
if repo_index_outdated(repo_item.directory):
|
||||
do_online_sync = True
|
||||
|
||||
return repos_notify, do_online_sync
|
||||
return repos_notify
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -593,11 +601,8 @@ def register():
|
||||
|
||||
if not bpy.app.background:
|
||||
if prefs.view.show_extensions_updates:
|
||||
repos_notify, do_online_sync = repos_to_notify()
|
||||
if repos_notify:
|
||||
from . import bl_extension_notify
|
||||
bl_extension_notify.update_non_blocking(repos=repos_notify, do_online_sync=do_online_sync)
|
||||
del repos_notify
|
||||
from . import bl_extension_notify
|
||||
bl_extension_notify.update_non_blocking(repos_fn=repos_to_notify)
|
||||
|
||||
|
||||
def unregister():
|
||||
|
||||
@@ -28,7 +28,9 @@ USE_GRACEFUL_EXIT = False
|
||||
# Special value to signal no packages can be updated because all repositories are blocked by being offline.
|
||||
STATE_DATA_ALL_OFFLINE = object()
|
||||
|
||||
WM_EXTENSIONS_CHECKING = -1
|
||||
# `wmWindowManager.extensions_updates` from C++
|
||||
WM_EXTENSIONS_UPDATE_UNSET = -2
|
||||
WM_EXTENSIONS_UPDATE_CHECKING = -1
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -142,7 +144,7 @@ def sync_apply_locked(repos_notify, repos_notify_files, unique_ext):
|
||||
return any_lock_errors, any_stale_errors
|
||||
|
||||
|
||||
def sync_status_generator(repos_notify, do_online_sync):
|
||||
def sync_status_generator(repos_fn):
|
||||
import atexit
|
||||
|
||||
# Generator results...
|
||||
@@ -153,18 +155,22 @@ def sync_status_generator(repos_notify, do_online_sync):
|
||||
# Setup The Update #
|
||||
# ################ #
|
||||
|
||||
repos_notify_orig = repos_notify
|
||||
if not bpy.app.online_access:
|
||||
repos_notify = [repo for repo in repos_notify if repo.remote_url.startswith("file://")]
|
||||
if not repos_notify:
|
||||
# Special case, early exit.
|
||||
yield (STATE_DATA_ALL_OFFLINE, 0, ())
|
||||
return
|
||||
|
||||
yield None
|
||||
|
||||
any_offline = len(repos_notify) != len(repos_notify_orig)
|
||||
del repos_notify_orig
|
||||
# Calculate the repositories.
|
||||
# This may be an expensive so yield once before running.
|
||||
repos_and_do_online = list(repos_fn())
|
||||
assert isinstance(repos_and_do_online, list)
|
||||
|
||||
if not bpy.app.online_access:
|
||||
# Allow file-system only sync.
|
||||
repos_and_do_online = [
|
||||
(repo, do_online_sync) for repo, do_online_sync in repos_and_do_online
|
||||
if repo.remote_url.startswith("file://")
|
||||
]
|
||||
|
||||
if not repos_and_do_online:
|
||||
return
|
||||
|
||||
# An extension unique to this session.
|
||||
unique_ext = "@{:x}".format(os.getpid())
|
||||
@@ -172,7 +178,7 @@ def sync_status_generator(repos_notify, do_online_sync):
|
||||
from functools import partial
|
||||
|
||||
cmd_batch_partial = []
|
||||
for repo_item in repos_notify:
|
||||
for repo_item, do_online_sync in repos_and_do_online:
|
||||
# Local only repositories should still refresh, but not run the sync.
|
||||
assert repo_item.remote_url
|
||||
cmd_batch_partial.append(partial(
|
||||
@@ -231,7 +237,7 @@ def sync_status_generator(repos_notify, do_online_sync):
|
||||
update_total = -1
|
||||
any_lock_errors = False
|
||||
|
||||
repos_notify_files = [[] for _ in repos_notify]
|
||||
repos_notify_files = [[] for _ in repos_and_do_online]
|
||||
|
||||
is_debug = bpy.app.debug
|
||||
while True:
|
||||
@@ -274,15 +280,13 @@ def sync_status_generator(repos_notify, do_online_sync):
|
||||
# ################### #
|
||||
# Finalize The Update #
|
||||
# ################### #
|
||||
repos_notify = [repo for repo, _do_online_sync in repos_and_do_online]
|
||||
any_lock_errors, any_stale_errors = sync_apply_locked(repos_notify, repos_notify_files, unique_ext)
|
||||
update_total = sync_status_count_outdated_extensions(repos_notify)
|
||||
if any_lock_errors:
|
||||
extra_warnings.append(" Failed to acquire lock!")
|
||||
if any_stale_errors:
|
||||
extra_warnings.append(" Unexpected change in repository!")
|
||||
|
||||
if any_offline:
|
||||
extra_warnings.append(" Skipping online repositories!")
|
||||
yield (cmd_batch.calc_status_data(), update_total, extra_warnings)
|
||||
else:
|
||||
yield None
|
||||
@@ -309,31 +313,28 @@ class NotifyHandle:
|
||||
"splash_region",
|
||||
|
||||
"sync_info",
|
||||
"do_online_sync",
|
||||
|
||||
"_repos",
|
||||
"_repos_fn",
|
||||
"is_complete",
|
||||
"_sync_generator",
|
||||
)
|
||||
|
||||
def __init__(self, repos_notify, do_online_sync):
|
||||
def __init__(self, repos_fn):
|
||||
self.splash_region = None
|
||||
self._repos = repos_notify
|
||||
self._repos_fn = repos_fn
|
||||
self._sync_generator = None
|
||||
self.is_complete = False
|
||||
# status_data, update_count, extra_warnings.
|
||||
self.sync_info = None
|
||||
self.do_online_sync = do_online_sync
|
||||
|
||||
def run(self):
|
||||
assert self._sync_generator is None
|
||||
self._sync_generator = iter(sync_status_generator(self._repos, self.do_online_sync))
|
||||
self._sync_generator = iter(sync_status_generator(self._repos_fn))
|
||||
|
||||
def run_ensure(self):
|
||||
if self.is_running():
|
||||
return False
|
||||
return
|
||||
self.run()
|
||||
return True
|
||||
|
||||
def run_step(self):
|
||||
assert self._sync_generator is not None
|
||||
@@ -349,17 +350,16 @@ class NotifyHandle:
|
||||
|
||||
def updates_count(self):
|
||||
if self.sync_info is None:
|
||||
return WM_EXTENSIONS_CHECKING
|
||||
return WM_EXTENSIONS_UPDATE_CHECKING
|
||||
_status_data, update_count, _extra_warnings = self.sync_info
|
||||
return update_count
|
||||
|
||||
def ui_text(self):
|
||||
if self.sync_info is None:
|
||||
return "Checking for Extension Updates", 'NONE', WM_EXTENSIONS_CHECKING
|
||||
return "Checking for Extension Updates", 'NONE', WM_EXTENSIONS_UPDATE_CHECKING
|
||||
status_data, update_count, extra_warnings = self.sync_info
|
||||
do_online_sync = self.do_online_sync
|
||||
text, icon = bl_extension_utils.CommandBatch.calc_status_text_icon_from_data(
|
||||
status_data, update_count, do_online_sync,
|
||||
status_data, update_count,
|
||||
)
|
||||
# Not more than 1-2 of these (failed to lock, some repositories offline .. etc).
|
||||
for warning in extra_warnings:
|
||||
@@ -408,12 +408,13 @@ def _ui_refresh_apply(*, notify):
|
||||
|
||||
def _ui_refresh_timer():
|
||||
if not _notify_queue:
|
||||
if wm.extensions_updates == WM_EXTENSIONS_UPDATE_CHECKING:
|
||||
wm.extensions_updates = WM_EXTENSIONS_UPDATE_UNSET
|
||||
return None
|
||||
|
||||
wm = bpy.context.window_manager
|
||||
notify = _notify_queue[0]
|
||||
if notify.run_ensure():
|
||||
wm.extensions_updates = WM_EXTENSIONS_CHECKING
|
||||
notify.run_ensure()
|
||||
|
||||
default_wait = TIME_WAIT_STEP
|
||||
|
||||
@@ -425,11 +426,19 @@ def _ui_refresh_timer():
|
||||
# Nothing changed, no action is needed (waiting for a response).
|
||||
return default_wait
|
||||
|
||||
# Some content was found, set checking.
|
||||
# Avoid doing this early because the icon flickers in cases when
|
||||
# it's not needed and it gets turned off quickly.
|
||||
if wm.extensions_updates == WM_EXTENSIONS_UPDATE_UNSET:
|
||||
wm.extensions_updates = WM_EXTENSIONS_UPDATE_CHECKING
|
||||
|
||||
# If the generator exited, either step to the next action or early exit here.
|
||||
if sync_info is ...:
|
||||
_ui_refresh_apply(notify=notify)
|
||||
if len(_notify_queue) <= 1:
|
||||
# Keep the item because the text should remain displayed for the splash.
|
||||
# Keep `_notify_queuy[0]` because the text should remain displayed for the splash.
|
||||
if wm.extensions_updates == WM_EXTENSIONS_UPDATE_CHECKING:
|
||||
wm.extensions_updates = WM_EXTENSIONS_UPDATE_UNSET
|
||||
return None
|
||||
# Move onto the next item.
|
||||
del _notify_queue[0]
|
||||
@@ -449,17 +458,16 @@ def _ui_refresh_timer():
|
||||
# Public API
|
||||
|
||||
|
||||
def update_non_blocking(*, repos, do_online_sync):
|
||||
"""
|
||||
Perform a non-blocking update on ``repos``.
|
||||
Updates are queued in case some are already running.
|
||||
"""
|
||||
# TODO: it's possible this preferences requests updates just after check-for-updates on startup.
|
||||
# The checks should be de-duplicated. For now just ensure the checks don't interfere with each other.
|
||||
assert bool(repos), "Unexpected empty repository list passed in"
|
||||
_notify_queue.append(NotifyHandle(repos, do_online_sync))
|
||||
def update_non_blocking(*, repos_fn):
|
||||
# Perform a non-blocking update on ``repos``.
|
||||
# Updates are queued in case some are already running.
|
||||
# `repos_fn` A generator or function that returns a list of ``(RepoItem, do_online_sync)`` pairs.
|
||||
# Some repositories don't check for update on startup for e.g.
|
||||
|
||||
_notify_queue.append(NotifyHandle(repos_fn))
|
||||
if not bpy.app.timers.is_registered(_ui_refresh_timer):
|
||||
bpy.app.timers.register(_ui_refresh_timer, first_interval=TIME_WAIT_INIT, persistent=True)
|
||||
return True
|
||||
|
||||
|
||||
def update_in_progress():
|
||||
|
||||
@@ -309,14 +309,13 @@ class notify_info:
|
||||
pass
|
||||
case None:
|
||||
from .bl_extension_notify import update_non_blocking
|
||||
if repos_notify := [repo for repo in repos if repo.remote_url]:
|
||||
update_non_blocking(repos=repos_notify, do_online_sync=True)
|
||||
notify_info._update_state = False
|
||||
# Starting.
|
||||
in_progress = True
|
||||
else:
|
||||
# Nothing to do, finished.
|
||||
notify_info._update_state = True
|
||||
# Assume nothing to do, finished.
|
||||
notify_info._update_state = True
|
||||
if repos_notify := [(repo, True) for repo in repos if repo.remote_url]:
|
||||
if update_non_blocking(repos_fn=lambda: repos_notify):
|
||||
# Correct assumption, starting.
|
||||
notify_info._update_state = False
|
||||
in_progress = True
|
||||
case False:
|
||||
from .bl_extension_notify import update_in_progress
|
||||
if update_in_progress():
|
||||
|
||||
@@ -878,7 +878,6 @@ class CommandBatch:
|
||||
def calc_status_text_icon_from_data(
|
||||
status_data: CommandBatch_StatusFlag,
|
||||
update_count: int,
|
||||
do_online_sync: bool,
|
||||
) -> Tuple[str, str]:
|
||||
# Generate a nice UI string for a status-bar & splash screen (must be short).
|
||||
#
|
||||
@@ -893,12 +892,11 @@ class CommandBatch:
|
||||
else:
|
||||
fail_text = ", some actions failed"
|
||||
|
||||
if status_data.flag == 1 << CommandBatchItem.STATUS_NOT_YET_STARTED or \
|
||||
status_data.flag & 1 << CommandBatchItem.STATUS_RUNNING:
|
||||
if do_online_sync:
|
||||
return "Checking for Extension Updates Online{:s}".format(fail_text), 'SORTTIME'
|
||||
else:
|
||||
return "Checking for Extension Updates{:s}".format(fail_text), 'SORTTIME'
|
||||
if (
|
||||
status_data.flag == (1 << CommandBatchItem.STATUS_NOT_YET_STARTED) or
|
||||
status_data.flag & (1 << CommandBatchItem.STATUS_RUNNING)
|
||||
):
|
||||
return "Checking for Extension Updates{:s}".format(fail_text), 'SORTTIME'
|
||||
|
||||
if status_data.flag == 1 << CommandBatchItem.STATUS_COMPLETE:
|
||||
if update_count > 0:
|
||||
|
||||
Reference in New Issue
Block a user