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
|
from . import pillar, gui
|
||||||
|
|
||||||
|
PILLAR_SERVER_URL = 'https://cloudapi.blender.org/'
|
||||||
|
# PILLAR_SERVER_URL = 'http://localhost:5000/'
|
||||||
|
|
||||||
ADDON_NAME = 'blender_cloud'
|
ADDON_NAME = 'blender_cloud'
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -23,8 +26,8 @@ class BlenderCloudPreferences(AddonPreferences):
|
|||||||
pillar_server = bpy.props.StringProperty(
|
pillar_server = bpy.props.StringProperty(
|
||||||
name='Blender Cloud Server',
|
name='Blender Cloud Server',
|
||||||
description='URL of the Blender Cloud backend server',
|
description='URL of the Blender Cloud backend server',
|
||||||
default='https://cloudapi.blender.org/',
|
default=PILLAR_SERVER_URL,
|
||||||
get=lambda self: 'https://cloudapi.blender.org/'
|
get=lambda self: PILLAR_SERVER_URL
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Move to the Scene properties?
|
# TODO: Move to the Scene properties?
|
||||||
@ -125,24 +128,16 @@ class PillarCredentialsUpdate(Operator):
|
|||||||
self.report({'ERROR'}, 'No active profile found')
|
self.report({'ERROR'}, 'No active profile found')
|
||||||
return {'CANCELLED'}
|
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:
|
try:
|
||||||
loop = asyncio.get_event_loop()
|
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:
|
except Exception as ex:
|
||||||
log.exception('Error in test call to Pillar')
|
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'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
self.report({'INFO'}, 'Blender Cloud credentials & endpoint URL updated.')
|
self.report({'INFO'}, 'Blender Cloud credentials & endpoint URL updated.')
|
||||||
|
@ -182,7 +182,7 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
|||||||
|
|
||||||
_draw_handle = None
|
_draw_handle = None
|
||||||
|
|
||||||
_state = 'BROWSING'
|
_state = 'INITIALIZING'
|
||||||
|
|
||||||
project_uuid = '5672beecc0261b2005ed1a33' # Blender Cloud project UUID
|
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.
|
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.current_display_content = []
|
||||||
self.loaded_images = set()
|
self.loaded_images = set()
|
||||||
self.browse_assets()
|
self.check_credentials()
|
||||||
|
|
||||||
context.window_manager.modal_handler_add(self)
|
context.window_manager.modal_handler_add(self)
|
||||||
self.timer = context.window_manager.event_timer_add(1 / 30, context.window)
|
self.timer = context.window_manager.event_timer_add(1 / 30, context.window)
|
||||||
@ -285,6 +285,35 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
|||||||
|
|
||||||
return {'RUNNING_MODAL'}
|
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):
|
def descend_node(self, node):
|
||||||
"""Descends the node hierarchy by visiting this node.
|
"""Descends the node hierarchy by visiting this node.
|
||||||
|
|
||||||
@ -386,7 +415,10 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
|||||||
else:
|
else:
|
||||||
raise ValueError('Unable to find MenuItem(node_uuid=%r)' % node_uuid)
|
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.log.info('Asynchronously downloading previews to %r', thumbnails_directory)
|
||||||
self.clear_images()
|
self.clear_images()
|
||||||
|
|
||||||
@ -396,7 +428,8 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
|||||||
def thumbnail_loaded(node, file_desc, thumb_path):
|
def thumbnail_loaded(node, file_desc, thumb_path):
|
||||||
self.update_menu_item(node, file_desc, thumb_path, file_desc['filename'])
|
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:
|
if self.node_uuid:
|
||||||
self.log.debug('Getting subnodes for parent node %r', 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,
|
children = await pillar.get_nodes(parent_node_uuid=self.node_uuid,
|
||||||
@ -435,9 +468,8 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
|||||||
future=self.signalling_future)
|
future=self.signalling_future)
|
||||||
|
|
||||||
def browse_assets(self):
|
def browse_assets(self):
|
||||||
self._state = 'BROWSING'
|
|
||||||
self.log.debug('Browsing assets at project %r node %r', self.project_uuid, self.node_uuid)
|
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):
|
def _new_async_task(self, async_task: asyncio.coroutine, future: asyncio.Future=None):
|
||||||
"""Stops the currently running async task, and starts another one."""
|
"""Stops the currently running async task, and starts another one."""
|
||||||
@ -457,6 +489,7 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
|||||||
"""Draws the GUI with OpenGL."""
|
"""Draws the GUI with OpenGL."""
|
||||||
|
|
||||||
drawers = {
|
drawers = {
|
||||||
|
'CHECKING_CREDENTIALS': self._draw_checking_credentials,
|
||||||
'BROWSING': self._draw_browser,
|
'BROWSING': self._draw_browser,
|
||||||
'DOWNLOADING_TEXTURE': self._draw_downloading,
|
'DOWNLOADING_TEXTURE': self._draw_downloading,
|
||||||
'EXCEPTION': self._draw_exception,
|
'EXCEPTION': self._draw_exception,
|
||||||
@ -531,14 +564,24 @@ class BlenderCloudBrowser(bpy.types.Operator):
|
|||||||
def _draw_downloading(self, context):
|
def _draw_downloading(self, context):
|
||||||
"""OpenGL drawing code for the DOWNLOADING_TEXTURE state."""
|
"""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.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)
|
bgl.glRectf(0, 0, content_width, content_height)
|
||||||
|
|
||||||
font_id = 0
|
font_id = 0
|
||||||
text = "Downloading texture from Blender Cloud"
|
|
||||||
bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
|
bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
|
||||||
blf.size(font_id, 20, 72)
|
blf.size(font_id, 20, 72)
|
||||||
text_width, text_height = blf.dimensions(font_id, text)
|
text_width, text_height = blf.dimensions(font_id, text)
|
||||||
|
@ -34,6 +34,13 @@ class UserNotLoggedInError(RuntimeError):
|
|||||||
return 'UserNotLoggedInError'
|
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):
|
class PillarError(RuntimeError):
|
||||||
"""Raised when there is some issue with the communication with Pillar.
|
"""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)
|
subclient = profile.subclients.get(SUBCLIENT_ID)
|
||||||
if not subclient:
|
if not subclient:
|
||||||
raise UserNotLoggedInError()
|
raise CredentialsNotSyncedError()
|
||||||
|
|
||||||
if _pillar_api is None:
|
if _pillar_api is None:
|
||||||
# Allow overriding the endpoint before importing Blender-specific stuff.
|
# 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,
|
_pillar_api = pillarsdk.Api(endpoint=pillar_endpoint,
|
||||||
username=subclient['subclient_user_id'],
|
username=subclient['subclient_user_id'],
|
||||||
password=None,
|
password=SUBCLIENT_ID,
|
||||||
token=subclient['token'])
|
token=subclient['token'])
|
||||||
|
|
||||||
return _pillar_api
|
return _pillar_api
|
||||||
@ -148,6 +155,51 @@ async def pillar_call(pillar_func, *args, **kwargs):
|
|||||||
return await loop.run_in_executor(None, partial)
|
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:
|
async def get_project_uuid(project_url: str) -> str:
|
||||||
"""Returns the UUID for the project, given its '/p/<project_url>' string."""
|
"""Returns the UUID for the project, given its '/p/<project_url>' string."""
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Primary requirements:
|
# Primary requirements:
|
||||||
CacheControl==0.11.6
|
CacheControl==0.11.6
|
||||||
lockfile==0.12.2
|
lockfile==0.12.2
|
||||||
pillarsdk==0.1.0
|
pillarsdk==0.1.1
|
||||||
wheel==0.29.0
|
wheel==0.29.0
|
||||||
|
|
||||||
# Secondary requirements:
|
# Secondary requirements:
|
||||||
|
Reference in New Issue
Block a user