Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- # -*- coding: UTF-8 -*-
- """
- Module delete_components_from_location: This module registers an action
- that allows the user to delete components from an entity in ftrack.
- """
- import argparse
- import getpass
- import logging
- import sys
- import traceback
- import ftrack
- import threading
- import os
- # add accessor to path and import it
- accessorPath = os.path.join(os.path.dirname(
- os.path.dirname(
- os.path.dirname(os.path.abspath(__file__)))), 'resolver')
- sys.path.append(accessorPath)
- # noinspection PyUnresolvedReferences
- import frostburnS3Accessor as fbS3
- def async(fn):
- """
- This decorator method allows for running *fn* asynchronously.
- :param fn:
- :return:
- """
- def wrapper(*args, **kwargs):
- thread = threading.Thread(target=fn, args=args, kwargs=kwargs)
- thread.start()
- thread.join()
- return wrapper
- class DeleteComponentsFromLocationAction(ftrack.Action):
- """
- This ``ftrack.Action`` subclass will allow the user to download all
- components from an entity.
- """
- # Action identifier
- identifier = 'delete-components-from-location'
- # Action label
- label = 'Delete Component(s)'
- # Current user from OS environment
- currentUser = getpass.getuser()
- def __init__( self ):
- super( DeleteComponentsFromLocationAction, self ).__init__( )
- self.componentsToDelete = None
- def validateRole(self, user, roleToCheck=None):
- """
- This method checks the user permissions.
- Returns ``True`` if user has specified ``ftrack.Role``. If no roleToCheck
- is specified, will return a ``list`` of all ``ftrack.Role``s that the user has.
- :param user: ``str`` that is the username.
- :param roleToCheck: ``str`` or ``ftrack.Role`` or ``tuple`` of roles
- as strings to check against, or ``None`` to return all roles.
- :return: ``True`` if user has roleToCheck, ``None`` if user does not.
- :rtype: ``True`` or ``None`` or ``ftrack.Role``
- """
- ftrackUser = ftrack.User(user)
- # get all roles this user has
- userRoles = ftrackUser.getRoles()
- def checkAgainstRole(userRolesToCheck, rolesToCheck):
- if isinstance(rolesToCheck, str):
- rolesToCheck = [rolesToCheck]
- for r in userRolesToCheck:
- if r.getName() in rolesToCheck:
- return True
- # return list of ftrack Roles if no roleToCheck was specified to check against
- if not roleToCheck:
- return userRoles
- else:
- # Check if user has specified roleToCheck
- if isinstance(roleToCheck, ftrack.Role):
- roleToCheck = roleToCheck.getName()
- result = checkAgainstRole(userRoles, roleToCheck)
- if result:
- return True
- else:
- return None
- def validateSelection(self, selection):
- """
- This method validates the user-selected entities in ftrack UI.
- Returns if *selection* is valid.
- Ensures that only Asset Versions, Task View and Show tab are valid selections.
- :param selection:
- :return:
- """
- if len(selection) >= 1 and \
- any(
- True for item in selection if
- item.get('entityType') in ('assetversion', 'task', 'show')
- ):
- return True
- else:
- return False
- def discover(self, event):
- """
- This method is invoked when the user brings up the Actions menu from the
- web UI. This action will only return the config when triggered on a single
- ``ftrack.AssetVersion`` object.
- """
- data = event['data']
- selection = data.get('selection', [])
- # validate selection
- if not self.validateSelection(selection):
- return
- # validate the user role
- if not self.validateRole(user=self.currentUser,
- roleToCheck=('Administrator', 'Project Manager', 'API')):
- return
- self.logger.info('Got selection: {0}'.format(selection))
- # Return data to show in web UI
- return super(DeleteComponentsFromLocationAction, self).discover(event)
- def launch( self, event ):
- """
- This method is invoked when the user executes the Action. This brings up
- a UI to choose where to save the components to.
- """
- # This returns a dictionary containing the data returned from the server.
- data = event['data']
- # Get the entity that was selected
- selection = data.get('selection', [])
- userId = event['source']['user']['id']
- # If user has executed action (since values have been returned)
- if 'values' in data:
- # Delete the components
- location = ftrack.Location(event['data']['values']['location'])
- self.deleteComponentsFromLocation(componentsToDelete=self.componentsToDelete,
- location=location,
- userId=userId)
- # Publish message to ftrack UI
- return {
- 'success': True,
- 'message': 'Deleting components...'
- }
- componentData = self.getComponentData(selection=selection, userId=userId)
- if componentData:
- components, locations = componentData
- self.componentsToDelete = components
- else:
- components = []
- locations = []
- # Format dict to be used in the Custom Actions UI
- uiItems = {
- 'items' : [
- {
- 'value' : '## Delete Components from Location ##',
- 'type' : 'label'
- },
- {
- 'value' : 'No. of Components on selection: {0}'
- .format(len(components)),
- 'type' : 'label'
- }
- ]
- }
- if locations:
- locationEnumUiItems = [
- {
- 'label' : location.getName(),
- 'value' : location.getId()
- }
- for location in locations
- ]
- locationUiItem = {
- 'label' : 'Location to remove from',
- 'type' : 'enumerator',
- 'name' : 'location',
- 'value' : locationEnumUiItems[0]['value'],
- 'data' : locationEnumUiItems
- }
- uiItems['items'].append(locationUiItem)
- # return dict with formatted data for displaying the custom actions ui
- return uiItems
- def register(self):
- """
- This overloads the base register() function to only subscribe to receive
- certain events that have user information in them.
- Used to filter on users that initiated the action.
- """
- self.logger.debug('register() called from: {0}'.format(__name__))
- # only subscribe/launch if this action's username match.
- ftrack.EVENT_HUB.subscribe(
- subscription='topic=ftrack.action.discover and source.user.username={0}'
- .format(self.currentUser),
- callback=self.discover
- )
- ftrack.EVENT_HUB.subscribe(
- subscription='topic=ftrack.action.launch and data.actionIdentifier={0} '
- 'and source.user.username={1}'
- .format(self.identifier, self.currentUser),
- callback=self.launch
- )
- def getVersionsInSelection(self, selection):
- """
- This method returns a list of the ``ftrack.AssetVersion`` objects that
- are associated with a *selection*.
- :param selection: ``list`` containing currently selected ftrack entities.
- :return: ``list`` containing ``ftrack.AssetVersion`` objects.
- :rtype: ``list``
- """
- versions = []
- # Iterate over selection and get the available versions
- for item in selection:
- self.logger.info(
- 'Looking for versions on entity ({0}, {1})'
- .format(item['entityId'], item['entityType'])
- )
- # handle Asset version entities
- if item['entityType'] == 'assetversion':
- versions.append(ftrack.AssetVersion(item['entityId']))
- continue
- # Get a valid entity type
- entity = None
- # Handle selection from the show tab
- if item['entityType'] == 'show':
- entity = ftrack.Project(item['entityId'])
- # Handle Task entities
- elif item['entityType'] == 'task':
- entity = ftrack.Task(item['entityId'])
- if not entity:
- continue
- assets = entity.getAssets(includeChildren=True)
- self.logger.info('Found {0} assets on entity'.format(len(assets)))
- # Grab each AssetVersion from each Asset
- for asset in assets:
- assetVersions = asset.getVersions()
- self.logger.info(
- 'Found {0} versions on asset {1}'.format(len(assetVersions), asset.getId())
- )
- versions.extend(assetVersions)
- self.logger.info('Found {0} versions in selection'.format(len(versions)))
- return versions
- def getLocationsOfComponents(self, components):
- """
- This method will return a list of all ``ftrack.Location``s that are found
- associated with a ``list`` of ``ftrack.Component``s.
- :param components: ``list`` containing ``ftrack.Components``
- :return: ``list`` containing ``ftrack.Location`` objects
- :rtype: ``list``
- """
- validLocations = []
- # get all possible locations for component to exist in
- locations = ftrack.getLocations(includeHidden=True, excludeInaccessible=True)
- if locations:
- # for each component, check valid locations that it exists in
- for component in components:
- assert isinstance(component, ftrack.Component), \
- '### {0} is not a ftrack.Component object!!!'.format(component)
- for location in locations:
- try:
- component.switchLocation(location)
- self.logger.debug('{0} exists in: {1}'.format(component, location))
- validLocations.append(location)
- except ftrack.ComponentNotInAnyLocationError:
- self.logger.warning('# {0} does not exist in any Location!'
- .format(component))
- except ftrack.LocationError:
- self.logger.debug('{0} does not exist in: {1}'
- .format(component, location))
- return validLocations
- def getComponentData(self, selection, userId, location='auto'):
- """
- This method returns a list of the ``ftrack.Component`` objects that
- are associated with a *selection*.
- :param selection: ``list`` containing currently selected ftrack entities.
- :param location: ``ftrack.Location`` object that Component should be switched to.
- use 'auto' to get first Location with 100% availability.
- :return: ``list`` containing ftrack.Component objects.
- :rtype: ``list``
- """
- # Create ftrack API job visible in the UI
- job = ftrack.createJob('Delete Components (Gathering data...)', 'running', user=userId)
- try:
- versions = self.getVersionsInSelection(selection)
- # Create list to append all found component objects to
- components = []
- for version in versions:
- self.logger.info('Looking for components on version {0}'.format(version.getId()))
- try:
- components.extend(version.getComponents(location=location))
- # Possible to trigger if Component is uploaded to different location (ftrackreview)
- except ftrack.ComponentNotInLocationError:
- self.logger.warning('# Not all Components were able to be found in: {0}'
- .format(location))
- # Only add components that are available in the specified location instance
- componentList = version.getComponents(location='auto')
- # todo: handle components from different locations such as ftrack.review
- for component in componentList:
- try:
- component.switchLocation(location)
- components.append(component)
- except ftrack.ComponentNotInAnyLocationError:
- self.logger.warning('# {0} was skipped because it does not exist in any Location!'
- .format(component.getName()))
- except ftrack.LocationError:
- self.logger.warning('# {0} was skipped because it does not exist in {1}!'
- .format(component.getName(), location))
- self.logger.info('Found {0} components in selection'.format(len(components)))
- locations = self.getLocationsOfComponents(components)
- # filter out all NoneType objects
- locations = list(set([location for location in locations
- if isinstance(location, ftrack.Location)]))
- except Exception:
- job.setStatus('failed')
- self.logger.exception(traceback.print_exc())
- raise RuntimeError
- # Finish API job
- job.setStatus('done')
- return components, locations
- @async
- def deleteComponentsFromLocation(self, componentsToDelete, location, userId):
- """
- This method deletes the components from the specified ``ftrack.Location`` instance.
- This is run asynchronously in order to ensure that the UI is responded to.
- """
- self.logger.debug('Deleting {0} from: {1}'.format(componentsToDelete, location))
- assert isinstance(location, ftrack.Location), \
- '### {0} is not a valid ftrack Location instance!!!'
- # Create ftrack API job visible in the UI
- job = ftrack.createJob('Delete Components (Deleting components...)', 'running', user=userId)
- try:
- numberOfComponents = len(componentsToDelete)
- self.logger.info('Deleting {0} components...'.format(numberOfComponents))
- # For each Component, remove it from location
- for idx, component in enumerate(componentsToDelete, start = 1):
- self.logger.info('Deleting component {0} of {1}...'.format(idx, numberOfComponents))
- job.setDescription('Deleting component {0} of {1}...'.format(idx, numberOfComponents))
- assert isinstance(component, ftrack.Component), \
- '### {0} is not a valid ftrack Component object!!!'.format(component)
- # switch location first to ensure it will be deleted correctly
- component.switchLocation(location)
- # Delete the component
- location.removeComponent(component)
- self.logger.info('Components deleted!')
- job.setStatus('done')
- except Exception:
- job.setStatus('failed')
- def register(registry, **kwargs):
- """
- Register action. Called when used as an event plugin.
- :param registry:
- :param kwargs:
- :return:
- """
- logger = logging.getLogger(__name__)
- logger.debug('Registering action: {0}'.format(
- os.path.abspath(os.path.dirname(__file__))))
- action = DeleteComponentsFromLocationAction()
- action.register()
- def main(arguments=None):
- """
- This entry point sets up logging and registers action when this is run
- as a standalone action.
- :param arguments:
- :return:
- """
- if arguments is None:
- arguments = []
- parser = argparse.ArgumentParser()
- # Allow setting of logging level from arguments.
- loggingLevels = {}
- for level in (
- logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
- logging.ERROR, logging.CRITICAL
- ):
- loggingLevels[logging.getLevelName(level).lower()] = level
- parser.add_argument(
- '-v', '--verbosity',
- help='Set the logging output verbosity.',
- choices=loggingLevels.keys(),
- default='info'
- )
- namespace = parser.parse_args(arguments)
- # Set up basic logging
- logging.basicConfig(level=loggingLevels[namespace.verbosity])
- # Subscribe to action.
- ftrack.setup()
- action = DeleteComponentsFromLocationAction()
- action.register()
- # Wait for events
- ftrack.EVENT_HUB.wait()
- if __name__ == '__main__':
- raise SystemExit(main(sys.argv[1:]))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement