Automatic refresh of subclient token.
This commit is contained in:
parent
f3699f651a
commit
1d662a0314
@ -11,6 +11,9 @@ from bpy.props import StringProperty
|
||||
|
||||
from . import pillar, gui
|
||||
|
||||
PILLAR_SERVER_URL = 'https://cloudapi.blender.org/'
|
||||
# PILLAR_SERVER_URL = 'http://localhost:5000/'
|
||||
|
||||
ADDON_NAME = 'blender_cloud'
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -23,8 +26,8 @@ class BlenderCloudPreferences(AddonPreferences):
|
||||
pillar_server = bpy.props.StringProperty(
|
||||
name='Blender Cloud Server',
|
||||
description='URL of the Blender Cloud backend server',
|
||||
default='https://cloudapi.blender.org/',
|
||||
get=lambda self: 'https://cloudapi.blender.org/'
|
||||
default=PILLAR_SERVER_URL,
|
||||
get=lambda self: PILLAR_SERVER_URL
|
||||
)
|
||||
|
||||
# TODO: Move to the Scene properties?
|
||||
@ -125,24 +128,16 @@ class PillarCredentialsUpdate(Operator):
|
||||
self.report({'ERROR'}, 'No active profile found')
|
||||
return {'CANCELLED'}
|
||||
|
||||
endpoint = preferences().pillar_server.rstrip('/')
|
||||
|
||||
# Create a subclient token and send it to Pillar.
|
||||
try:
|
||||
blender_id.create_subclient_token(pillar.SUBCLIENT_ID, endpoint)
|
||||
except blender_id.BlenderIdCommError as ex:
|
||||
log.exception('Error sending subclient-specific token to Blender ID')
|
||||
self.report({'ERROR'}, 'Failed to sync Blender ID to %s' % endpoint)
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Test the new URL
|
||||
pillar._pillar_api = None
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(pillar.get_project_uuid('textures')) # Any query will do.
|
||||
loop.run_until_complete(pillar.refresh_pillar_credentials())
|
||||
except blender_id.BlenderIdCommError as ex:
|
||||
log.exception('Error sending subclient-specific token to Blender ID')
|
||||
self.report({'ERROR'}, 'Failed to sync Blender ID to Blender Cloud')
|
||||
return {'CANCELLED'}
|
||||
except Exception as ex:
|
||||
log.exception('Error in test call to Pillar')
|
||||
self.report({'ERROR'}, 'Failed test connection to %s' % endpoint)
|
||||
self.report({'ERROR'}, 'Failed test connection to Blender Cloud')
|
||||
return {'CANCELLED'}
|
||||
|
||||
self.report({'INFO'}, 'Blender Cloud credentials & endpoint URL updated.')
|
||||
|
@ -182,7 +182,7 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
||||
|
||||
_draw_handle = None
|
||||
|
||||
_state = 'BROWSING'
|
||||
_state = 'INITIALIZING'
|
||||
|
||||
project_uuid = '5672beecc0261b2005ed1a33' # Blender Cloud project UUID
|
||||
node = None # The Node object we're currently showing, or None if we're at the project top.
|
||||
@ -229,7 +229,7 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
||||
|
||||
self.current_display_content = []
|
||||
self.loaded_images = set()
|
||||
self.browse_assets()
|
||||
self.check_credentials()
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
self.timer = context.window_manager.event_timer_add(1 / 30, context.window)
|
||||
@ -285,6 +285,35 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def check_credentials(self):
|
||||
self._state = 'CHECKING_CREDENTIALS'
|
||||
self.log.debug('Checking credentials')
|
||||
self._new_async_task(self._check_credentials())
|
||||
|
||||
async def _check_credentials(self):
|
||||
"""Checks credentials with Pillar, and if ok goes to the BROWSING state."""
|
||||
|
||||
try:
|
||||
await pillar.check_pillar_credentials()
|
||||
except pillar.CredentialsNotSyncedError:
|
||||
self.log.info('Credentials not synced, re-syncing automatically.')
|
||||
else:
|
||||
self.log.info('Credentials okay, browsing assets.')
|
||||
await self.async_download_previews()
|
||||
return
|
||||
|
||||
try:
|
||||
await pillar.refresh_pillar_credentials()
|
||||
except pillar.UserNotLoggedInError:
|
||||
self.error('User not logged in on Blender ID.')
|
||||
else:
|
||||
self.log.info('Credentials refreshed and ok, browsing assets.')
|
||||
await self.async_download_previews()
|
||||
return
|
||||
|
||||
raise pillar.UserNotLoggedInError()
|
||||
# self._new_async_task(self._check_credentials())
|
||||
|
||||
def descend_node(self, node):
|
||||
"""Descends the node hierarchy by visiting this node.
|
||||
|
||||
@ -386,7 +415,10 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
||||
else:
|
||||
raise ValueError('Unable to find MenuItem(node_uuid=%r)' % node_uuid)
|
||||
|
||||
async def async_download_previews(self, thumbnails_directory):
|
||||
async def async_download_previews(self):
|
||||
self._state = 'BROWSING'
|
||||
|
||||
thumbnails_directory = self.thumbnails_cache
|
||||
self.log.info('Asynchronously downloading previews to %r', thumbnails_directory)
|
||||
self.clear_images()
|
||||
|
||||
@ -396,7 +428,8 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
||||
def thumbnail_loaded(node, file_desc, thumb_path):
|
||||
self.update_menu_item(node, file_desc, thumb_path, file_desc['filename'])
|
||||
|
||||
# Download either by group_texture node UUID or by project UUID (which shows all top-level nodes)
|
||||
# Download either by group_texture node UUID or by project UUID (which
|
||||
# shows all top-level nodes)
|
||||
if self.node_uuid:
|
||||
self.log.debug('Getting subnodes for parent node %r', self.node_uuid)
|
||||
children = await pillar.get_nodes(parent_node_uuid=self.node_uuid,
|
||||
@ -435,9 +468,8 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
||||
future=self.signalling_future)
|
||||
|
||||
def browse_assets(self):
|
||||
self._state = 'BROWSING'
|
||||
self.log.debug('Browsing assets at project %r node %r', self.project_uuid, self.node_uuid)
|
||||
self._new_async_task(self.async_download_previews(self.thumbnails_cache))
|
||||
self._new_async_task(self.async_download_previews())
|
||||
|
||||
def _new_async_task(self, async_task: asyncio.coroutine, future: asyncio.Future=None):
|
||||
"""Stops the currently running async task, and starts another one."""
|
||||
@ -457,6 +489,7 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
||||
"""Draws the GUI with OpenGL."""
|
||||
|
||||
drawers = {
|
||||
'CHECKING_CREDENTIALS': self._draw_checking_credentials,
|
||||
'BROWSING': self._draw_browser,
|
||||
'DOWNLOADING_TEXTURE': self._draw_downloading,
|
||||
'EXCEPTION': self._draw_exception,
|
||||
@ -531,14 +564,24 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
||||
def _draw_downloading(self, context):
|
||||
"""OpenGL drawing code for the DOWNLOADING_TEXTURE state."""
|
||||
|
||||
content_height, content_width = self._window_size(context)
|
||||
self._draw_text_on_colour(context,
|
||||
'Downloading texture from Blender Cloud',
|
||||
(0.0, 0.0, 0.2, 0.6))
|
||||
|
||||
def _draw_checking_credentials(self, context):
|
||||
"""OpenGL drawing code for the CHECKING_CREDENTIALS state."""
|
||||
|
||||
self._draw_text_on_colour(context,
|
||||
'Checking login credentials',
|
||||
(0.0, 0.0, 0.2, 0.6))
|
||||
|
||||
def _draw_text_on_colour(self, context, text, bgcolour):
|
||||
content_height, content_width = self._window_size(context)
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glColor4f(0.0, 0.0, 0.2, 0.6)
|
||||
bgl.glColor4f(*bgcolour)
|
||||
bgl.glRectf(0, 0, content_width, content_height)
|
||||
|
||||
font_id = 0
|
||||
text = "Downloading texture from Blender Cloud"
|
||||
bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
|
||||
blf.size(font_id, 20, 72)
|
||||
text_width, text_height = blf.dimensions(font_id, text)
|
||||
|
@ -34,6 +34,13 @@ class UserNotLoggedInError(RuntimeError):
|
||||
return 'UserNotLoggedInError'
|
||||
|
||||
|
||||
class CredentialsNotSyncedError(UserNotLoggedInError):
|
||||
"""Raised when the user may be logged in on Blender ID, but has no Blender Cloud token."""
|
||||
|
||||
def __str__(self):
|
||||
return 'CredentialsNotSyncedError'
|
||||
|
||||
|
||||
class PillarError(RuntimeError):
|
||||
"""Raised when there is some issue with the communication with Pillar.
|
||||
|
||||
@ -118,7 +125,7 @@ def pillar_api(pillar_endpoint: str = None) -> pillarsdk.Api:
|
||||
|
||||
subclient = profile.subclients.get(SUBCLIENT_ID)
|
||||
if not subclient:
|
||||
raise UserNotLoggedInError()
|
||||
raise CredentialsNotSyncedError()
|
||||
|
||||
if _pillar_api is None:
|
||||
# Allow overriding the endpoint before importing Blender-specific stuff.
|
||||
@ -130,7 +137,7 @@ def pillar_api(pillar_endpoint: str = None) -> pillarsdk.Api:
|
||||
|
||||
_pillar_api = pillarsdk.Api(endpoint=pillar_endpoint,
|
||||
username=subclient['subclient_user_id'],
|
||||
password=None,
|
||||
password=SUBCLIENT_ID,
|
||||
token=subclient['token'])
|
||||
|
||||
return _pillar_api
|
||||
@ -148,6 +155,51 @@ async def pillar_call(pillar_func, *args, **kwargs):
|
||||
return await loop.run_in_executor(None, partial)
|
||||
|
||||
|
||||
async def check_pillar_credentials():
|
||||
"""Tries to obtain the user at Pillar using the user's credentials.
|
||||
|
||||
:raises UserNotLoggedInError: when the user is not logged in on Blender ID.
|
||||
:raises CredentialsNotSyncedError: when the user is logged in on Blender ID but
|
||||
doesn't have a valid subclient token for Pillar.
|
||||
"""
|
||||
|
||||
profile = blender_id_profile()
|
||||
if not profile:
|
||||
raise UserNotLoggedInError()
|
||||
|
||||
subclient = profile.subclients.get(SUBCLIENT_ID)
|
||||
if not subclient:
|
||||
raise CredentialsNotSyncedError()
|
||||
|
||||
try:
|
||||
await get_project_uuid('textures') # Any query will do.
|
||||
except pillarsdk.UnauthorizedAccess:
|
||||
raise CredentialsNotSyncedError()
|
||||
|
||||
|
||||
async def refresh_pillar_credentials():
|
||||
"""Refreshes the authentication token on Pillar.
|
||||
|
||||
:raises blender_id.BlenderIdCommError: when Blender ID refuses to send a token to Pillar.
|
||||
:raises Exception: when the Pillar credential check fails.
|
||||
"""
|
||||
|
||||
global _pillar_api
|
||||
|
||||
import blender_id
|
||||
|
||||
from . import blender
|
||||
pillar_endpoint = blender.preferences().pillar_server.rstrip('/')
|
||||
|
||||
# Create a subclient token and send it to Pillar.
|
||||
# May raise a blender_id.BlenderIdCommError
|
||||
blender_id.create_subclient_token(SUBCLIENT_ID, pillar_endpoint)
|
||||
|
||||
# Test the new URL
|
||||
_pillar_api = None
|
||||
await get_project_uuid('textures') # Any query will do.
|
||||
|
||||
|
||||
async def get_project_uuid(project_url: str) -> str:
|
||||
"""Returns the UUID for the project, given its '/p/<project_url>' string."""
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Primary requirements:
|
||||
CacheControl==0.11.6
|
||||
lockfile==0.12.2
|
||||
pillarsdk==0.1.0
|
||||
pillarsdk==0.1.1
|
||||
wheel==0.29.0
|
||||
|
||||
# Secondary requirements:
|
||||
|
Reference in New Issue
Block a user