Automatic refresh of subclient token.

This commit is contained in:
Sybren A. Stüvel 2016-05-04 14:30:47 +02:00
parent f3699f651a
commit 1d662a0314
4 changed files with 118 additions and 28 deletions

View File

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

View File

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

View File

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

View File

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