Assets: add function to cancel all remote asset listing downloads

Add a Python function to cancel all remote asset listing downloads.

Calling this function will leave the on-disk listings in an unusable
state. This isn't user friendly, so in the future it would be nice to
store a backup of the listing's files, and restore those on
cancellation. That is for a future PR, though, as the current limitation
already exists when quitting Blender while the listing is being
downloaded.

Pull Request: https://projects.blender.org/blender/blender/pulls/158495
This commit is contained in:
Sybren A. Stüvel 2026-05-14 16:32:49 +02:00
parent 179c543220
commit 85fab554bb
2 changed files with 35 additions and 8 deletions

View file

@ -485,6 +485,20 @@ def remote_asset_library_sync(
_downloaders.append(downloader)
def remote_asset_library_sync_cancel() -> None:
"""Cancel all remote asset library sync operations.
This will cancel all running downloads & shut down the downloaders. Any
partially-downloaded listing will have to be re-downloaded to be fully
correct again.
"""
# This calls the relevant _remote_asset_library_sync_done() functions as well, ensuring that each downloader is
# properly cleaned up. That includes removing items from _downloaders, hence the copy of that list.
for downloader in _downloaders[:]:
downloader.cancel_and_shutdown()
def _remote_asset_library_sync_done(downloader: _RemoteAssetListingDownloader) -> None:
"""
Called when the downloading of the remote asset listing is done.

View file

@ -541,7 +541,8 @@ class RemoteAssetListingDownloader:
def _shutdown_if_done(self) -> None:
if self._num_asset_pages_pending == 0 and self._bg_downloader.all_downloads_done:
# Done downloading everything, let's shut down.
self.shutdown(DownloadStatus.FINISHED_SUCCESSFULLY)
self._status = DownloadStatus.FINISHED_SUCCESSFULLY
self.shutdown()
@staticmethod
def _cache_bust_stamp(*, _mocked_now: _datetime | None = None) -> int:
@ -631,7 +632,8 @@ class RemoteAssetListingDownloader:
"exception while handling downloaded file ({!r}, saved to {!r})".format(
http_req_descr, local_file))
self.report({'ERROR'}, "Asset library index had an issue, download aborted")
self.shutdown(DownloadStatus.FAILED)
self._status = DownloadStatus.FAILED
self.shutdown()
def _queue_download(
self,
@ -660,10 +662,18 @@ class RemoteAssetListingDownloader:
if 'ERROR' in level:
self._error_message = message
def shutdown(self, status: DownloadStatus) -> None:
"""Stop the background downloader, update the status and call the 'done' callback."""
def cancel_and_shutdown(self) -> None:
"""Cancel all downloads and shut down the background downloader."""
self._status = status
if self._status == DownloadStatus.LOADING:
self._status = DownloadStatus.FAILED
# The downloads themselves don't have to be explicitly cancelled,
# shutting down the downloader will do that implicitly.
self.shutdown()
def shutdown(self) -> None:
"""Stop the background downloader and call the 'done' callback."""
# The timer is no longer necessary, the bg_downloader.shutdown() call
# takes care of the last queued messages.
@ -693,7 +703,8 @@ class RemoteAssetListingDownloader:
self._bg_downloader.update()
except http_dl.BackgroundProcessNotRunningError:
logger.error("Background downloader subprocess died, aborting.")
self.shutdown(DownloadStatus.FAILED)
self._status = DownloadStatus.FAILED
self.shutdown()
return 0 # Deactivate the timer.
except Exception:
logger.exception(
@ -744,12 +755,14 @@ class RemoteAssetListingDownloader:
if self._num_asset_pages_pending:
self.report({'WARNING'}, "Cancelled {} pending download".format(self._num_asset_pages_pending))
logger.warning("Download cancelled: %s", http_req_descr)
self.shutdown(DownloadStatus.FAILED)
self._status = DownloadStatus.FAILED
self.shutdown()
return
self.report({'ERROR'}, "Error downloading {}: {}".format(http_req_descr.url, error))
logger.error("Error downloading %s: %s", http_req_descr, error)
self.shutdown(DownloadStatus.FAILED)
self._status = DownloadStatus.FAILED
self.shutdown()
def download_progress(
self,