From 5eaee872bf9f0493f90065c89396f1c33ea774a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 10 May 2016 14:52:51 +0200 Subject: [PATCH] Added check for user's roles -- disallow usage by non-subscribers. This makes it clear from the get-go that users need to subscribe. Otherwise they'll get unexpected errors once they try to download something. --- blender_cloud/gui.py | 69 ++++++++++++++++++++++++++++++++--------- blender_cloud/pillar.py | 24 +++++++++++--- 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/blender_cloud/gui.py b/blender_cloud/gui.py index 9838e63..106cf05 100644 --- a/blender_cloud/gui.py +++ b/blender_cloud/gui.py @@ -269,24 +269,36 @@ class BlenderCloudBrowser(bpy.types.Operator): self.mouse_x = event.mouse_x self.mouse_y = event.mouse_y - if self._state == 'BROWSING' and event.type == 'LEFTMOUSE' and event.value == 'RELEASE': + left_mouse_release = event.type == 'LEFTMOUSE' and event.value == 'RELEASE' + if self._state == 'PLEASE_SUBSCRIBE' and left_mouse_release: + self.open_browser_subscribe() + self._finish(context) + return {'FINISHED'} + + if self._state == 'BROWSING': selected = self.get_clicked() - if selected is None: - # No item clicked, ignore it. - return {'RUNNING_MODAL'} - - if selected.is_folder: - self.descend_node(selected.node) + if selected: + context.window.cursor_set('HAND') else: - if selected.file_desc is None: - # This can happen when the thumbnail information isn't loaded yet. - # Just ignore the click for now. - # TODO: think of a way to handle this properly. - return {'RUNNING_MODAL'} - self.handle_item_selection(context, selected) + context.window.cursor_set('DEFAULT') - elif event.type in {'RIGHTMOUSE', 'ESC'}: + if left_mouse_release: + if selected is None: + # No item clicked, ignore it. + return {'RUNNING_MODAL'} + + if selected.is_folder: + self.descend_node(selected.node) + else: + if selected.file_desc is None: + # This can happen when the thumbnail information isn't loaded yet. + # Just ignore the click for now. + # TODO: think of a way to handle this properly. + return {'RUNNING_MODAL'} + self.handle_item_selection(context, selected) + + if event.type in {'RIGHTMOUSE', 'ESC'}: self._finish(context) return {'CANCELLED'} @@ -302,6 +314,10 @@ class BlenderCloudBrowser(bpy.types.Operator): try: await pillar.check_pillar_credentials() + except pillar.NotSubscribedToCloudError: + self.log.info('User not subscribed to Blender Cloud.') + self._show_subscribe_screen() + return except pillar.CredentialsNotSyncedError: self.log.info('Credentials not synced, re-syncing automatically.') else: @@ -311,6 +327,10 @@ class BlenderCloudBrowser(bpy.types.Operator): try: await pillar.refresh_pillar_credentials() + except pillar.NotSubscribedToCloudError: + self.log.info('User is not a Blender Cloud subscriber.') + self._show_subscribe_screen() + return except pillar.UserNotLoggedInError: self.error('User not logged in on Blender ID.') else: @@ -321,6 +341,12 @@ class BlenderCloudBrowser(bpy.types.Operator): raise pillar.UserNotLoggedInError() # self._new_async_task(self._check_credentials()) + def _show_subscribe_screen(self): + """Shows the "You need to subscribe" screen.""" + + self._state = 'PLEASE_SUBSCRIBE' + bpy.context.window.cursor_set('HAND') + def descend_node(self, node): """Descends the node hierarchy by visiting this node. @@ -479,7 +505,7 @@ class BlenderCloudBrowser(bpy.types.Operator): self.log.debug('Browsing assets at project %r node %r', self.project_uuid, self.node_uuid) 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.""" self.log.debug('Setting up a new task %r, so any existing task must be stopped', async_task) @@ -501,6 +527,7 @@ class BlenderCloudBrowser(bpy.types.Operator): 'BROWSING': self._draw_browser, 'DOWNLOADING_TEXTURE': self._draw_downloading, 'EXCEPTION': self._draw_exception, + 'PLEASE_SUBSCRIBE': self._draw_subscribe, } if self._state in drawers: @@ -643,6 +670,11 @@ class BlenderCloudBrowser(bpy.types.Operator): blf.draw(font_id, line) bgl.glDisable(bgl.GL_BLEND) + def _draw_subscribe(self, context): + self._draw_text_on_colour(context, + 'Click to subscribe to the Blender Cloud', + (0.0, 0.0, 0.2, 0.6)) + def get_clicked(self) -> MenuItem: for item in self.current_display_content: @@ -691,6 +723,13 @@ class BlenderCloudBrowser(bpy.types.Operator): future=signalling_future)) self.async_task.add_done_callback(texture_download_completed) + def open_browser_subscribe(self): + import webbrowser + + webbrowser.open_new_tab('https://cloud.blender.org/join') + + self.report({'INFO'}, 'We just started a browser for you.') + # store keymaps here to access after registration addon_keymaps = [] diff --git a/blender_cloud/pillar.py b/blender_cloud/pillar.py index 193a444..ba160df 100644 --- a/blender_cloud/pillar.py +++ b/blender_cloud/pillar.py @@ -31,14 +31,15 @@ class UserNotLoggedInError(RuntimeError): """ def __str__(self): - return 'UserNotLoggedInError' + return self.__class__.__name__ 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 NotSubscribedToCloudError(UserNotLoggedInError): + """Raised when the user may be logged in on Blender ID, but has no Blender Cloud token.""" class PillarError(RuntimeError): @@ -171,11 +172,24 @@ async def check_pillar_credentials(): if not subclient: raise CredentialsNotSyncedError() + pillar_user_id = subclient['subclient_user_id'] + if not pillar_user_id: + raise CredentialsNotSyncedError() + try: - await get_project_uuid('textures') # Any query will do. + db_user = await pillar_call(pillarsdk.User.find, pillar_user_id) except pillarsdk.UnauthorizedAccess: raise CredentialsNotSyncedError() + roles = db_user.roles + log.debug('User has roles %r', roles) + if not roles or not {'subscriber', 'demo'}.intersection(set(roles)): + # Delete the subclient info. This forces a re-check later, which can + # then pick up on the user's new status. + del profile.subclients[SUBCLIENT_ID] + profile.save_json() + raise NotSubscribedToCloudError() + async def refresh_pillar_credentials(): """Refreshes the authentication token on Pillar. @@ -197,7 +211,7 @@ async def refresh_pillar_credentials(): # Test the new URL _pillar_api = None - await get_project_uuid('textures') # Any query will do. + await check_pillar_credentials() async def get_project_uuid(project_url: str) -> str: