diff --git a/plugins/DigitalLibrary/src/DigitalFactoryApiClient.py b/plugins/DigitalLibrary/src/DigitalFactoryApiClient.py index b0e34adaba..0140d9858d 100644 --- a/plugins/DigitalLibrary/src/DigitalFactoryApiClient.py +++ b/plugins/DigitalLibrary/src/DigitalFactoryApiClient.py @@ -22,6 +22,7 @@ from .DFFileUploader import DFFileUploader from .DFLibraryFileUploadRequest import DFLibraryFileUploadRequest from .DFLibraryFileUploadResponse import DFLibraryFileUploadResponse from .DFPrintJobUploadRequest import DFPrintJobUploadRequest +from .DigitalFactoryFeatureBudgetResponse import DigitalFactoryFeatureBudgetResponse from .DigitalFactoryFileResponse import DigitalFactoryFileResponse from .DigitalFactoryProjectResponse import DigitalFactoryProjectResponse from .PaginationLinks import PaginationLinks @@ -57,6 +58,26 @@ class DigitalFactoryApiClient: self._projects_pagination_mgr = PaginationManager(limit = projects_limit_per_page) if projects_limit_per_page else None # type: Optional[PaginationManager] + def checkUserHasAccess(self, callback: Callable) -> bool: + """Checks if the user has any sort of access to the digital library. + A user is considered to have access if the max-# of private projects is greater then 0 (or -1 for unlimited). + """ + + def callbackWrap(response: Optional[Any] = None, *args, **kwargs) -> None: + if response and isinstance(response, DigitalFactoryFeatureBudgetResponse): + callback( + response.library_max_private_projects == -1 or # Note: -1 is unlimited + response.library_max_private_projects > 0) + else: + Logger.warning(f"Digital Factory: Response is not a feature budget, likely an error: {str(response)}") + callback(False) + + self._http.get(f"{self.CURA_API_ROOT}/feature_budgets", + scope = self._scope, + callback = self._parseCallback(callbackWrap, DigitalFactoryFeatureBudgetResponse, callbackWrap), + error_callback = callbackWrap, + timeout = self.DEFAULT_REQUEST_TIMEOUT) + def getProject(self, library_project_id: str, on_finished: Callable[[DigitalFactoryProjectResponse], Any], failed: Callable) -> None: """ Retrieves a digital factory project by its library project id. diff --git a/plugins/DigitalLibrary/src/DigitalFactoryController.py b/plugins/DigitalLibrary/src/DigitalFactoryController.py index 352a8c70f2..368b29219a 100644 --- a/plugins/DigitalLibrary/src/DigitalFactoryController.py +++ b/plugins/DigitalLibrary/src/DigitalFactoryController.py @@ -89,6 +89,9 @@ class DigitalFactoryController(QObject): uploadFileError = Signal() uploadFileFinished = Signal() + """Signal to inform about the state of user access.""" + userAccessStateChanged = pyqtSignal(bool) + def __init__(self, application: CuraApplication) -> None: super().__init__(parent = None) @@ -106,6 +109,7 @@ class DigitalFactoryController(QObject): self._has_more_projects_to_load = False self._account = self._application.getInstance().getCuraAPI().account # type: Account + self._account.loginStateChanged.connect(self._onLoginStateChanged) self._current_workspace_information = CuraApplication.getInstance().getCurrentWorkspaceInformation() # Initialize the project model @@ -131,6 +135,8 @@ class DigitalFactoryController(QObject): self._application.engineCreatedSignal.connect(self._onEngineCreated) self._application.initializationFinished.connect(self._applicationInitializationFinished) + self._user_has_access = False + def clear(self) -> None: self._project_model.clearProjects() self._api.clear() @@ -143,16 +149,24 @@ class DigitalFactoryController(QObject): self.setSelectedProjectIndex(-1) + def _onLoginStateChanged(self, logged_in: bool) -> None: + def callback(has_access, **kwargs): + self._user_has_access = has_access + self.userAccessStateChanged.emit(logged_in) + + self._api.checkUserHasAccess(callback) + def userAccountHasLibraryAccess(self) -> bool: """ Checks whether the currently logged in user account has access to the Digital Library :return: True if the user account has Digital Library access, else False """ - subscriptions = [] # type: List[Dict[str, Any]] if self._account.userProfile: subscriptions = self._account.userProfile.get("subscriptions", []) - return len(subscriptions) > 0 + if len(subscriptions) > 0: + return True + return self._user_has_access def initialize(self, preselected_project_id: Optional[str] = None) -> None: self.clear() diff --git a/plugins/DigitalLibrary/src/DigitalFactoryFeatureBudgetResponse.py b/plugins/DigitalLibrary/src/DigitalFactoryFeatureBudgetResponse.py new file mode 100644 index 0000000000..016306a478 --- /dev/null +++ b/plugins/DigitalLibrary/src/DigitalFactoryFeatureBudgetResponse.py @@ -0,0 +1,43 @@ +# Copyright (c) 2021 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from .BaseModel import BaseModel +from typing import Optional + + +class DigitalFactoryFeatureBudgetResponse(BaseModel): + """Class representing the capabilities of a user account for Digital Library. + NOTE: For each max_..._projects fields, '-1' means unlimited! + """ + + def __init__(self, + library_can_use_business_value: Optional[bool] = False, + library_can_use_comments: Optional[bool] = False, + library_can_use_status: Optional[bool] = False, + library_can_use_tags: Optional[bool] = False, + library_can_use_technical_requirements: Optional[bool] = False, + library_max_organization_shared_projects: Optional[int] = False, # -1 means unlimited + library_max_private_projects: Optional[int] = False, # -1 means unlimited + library_max_team_shared_projects: Optional[int] = False, # -1 means unlimited + **kwargs) -> None: + + self.library_can_use_business_value = library_can_use_business_value + self.library_can_use_comments = library_can_use_comments + self.library_can_use_status = library_can_use_status + self.library_can_use_tags = library_can_use_tags + self.library_can_use_technical_requirements = library_can_use_technical_requirements + self.library_max_organization_shared_projects = library_max_organization_shared_projects # -1 means unlimited + self.library_max_private_projects = library_max_private_projects # -1 means unlimited + self.library_max_team_shared_projects = library_max_team_shared_projects # -1 means unlimited + super().__init__(**kwargs) + + def __repr__(self) -> str: + return "max private: {}, max org: {}, max team: {}".format( + self.library_max_private_projects, + self.library_max_organization_shared_projects, + self.library_max_team_shared_projects) + + # Validates the model, raising an exception if the model is invalid. + def validate(self) -> None: + super().validate() + # No validation for now, as the response can be "data: []", which should be interpreted as all False and 0's diff --git a/plugins/DigitalLibrary/src/DigitalFactoryFileProvider.py b/plugins/DigitalLibrary/src/DigitalFactoryFileProvider.py index 7a544afaa1..65a727e21a 100644 --- a/plugins/DigitalLibrary/src/DigitalFactoryFileProvider.py +++ b/plugins/DigitalLibrary/src/DigitalFactoryFileProvider.py @@ -22,7 +22,7 @@ class DigitalFactoryFileProvider(FileProvider): self._dialog = None self._account = CuraApplication.getInstance().getCuraAPI().account # type: Account - self._account.loginStateChanged.connect(self._onLoginStateChanged) + self._controller.userAccessStateChanged.connect(self._onUserAccessStateChanged) self.enabled = self._account.isLoggedIn and self._controller.userAccountHasLibraryAccess() self.priority = 10 @@ -53,7 +53,7 @@ class DigitalFactoryFileProvider(FileProvider): if not self._dialog: Logger.log("e", "Unable to create the Digital Library Open dialog.") - def _onLoginStateChanged(self, logged_in: bool) -> None: + def _onUserAccessStateChanged(self, logged_in: bool) -> None: """ Sets the enabled status of the DigitalFactoryFileProvider according to the account's login status :param logged_in: The new login status diff --git a/plugins/DigitalLibrary/src/DigitalFactoryOutputDevice.py b/plugins/DigitalLibrary/src/DigitalFactoryOutputDevice.py index 202223f9b4..70e3ac34f2 100644 --- a/plugins/DigitalLibrary/src/DigitalFactoryOutputDevice.py +++ b/plugins/DigitalLibrary/src/DigitalFactoryOutputDevice.py @@ -45,7 +45,7 @@ class DigitalFactoryOutputDevice(ProjectOutputDevice): self._writing = False self._account = CuraApplication.getInstance().getCuraAPI().account # type: Account - self._account.loginStateChanged.connect(self._onLoginStateChanged) + self._controller.userAccessStateChanged.connect(self._onUserAccessStateChanged) self.enabled = self._account.isLoggedIn and self._controller.userAccountHasLibraryAccess() self._current_workspace_information = CuraApplication.getInstance().getCurrentWorkspaceInformation() @@ -97,7 +97,7 @@ class DigitalFactoryOutputDevice(ProjectOutputDevice): if not self._dialog: Logger.log("e", "Unable to create the Digital Library Save dialog.") - def _onLoginStateChanged(self, logged_in: bool) -> None: + def _onUserAccessStateChanged(self, logged_in: bool) -> None: """ Sets the enabled status of the DigitalFactoryOutputDevice according to the account's login status :param logged_in: The new login status