Advertisement
ylSiew

Untitled

May 19th, 2015
230
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 17.29 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3.  
  4. """
  5. Module delete_components_from_location: This module registers an action
  6. that allows the user to delete components from an entity in ftrack.
  7. """
  8.  
  9. import argparse
  10. import getpass
  11. import logging
  12. import sys
  13. import traceback
  14. import ftrack
  15. import threading
  16. import os
  17.  
  18.  
  19. # add accessor to path and import it
  20. accessorPath = os.path.join(os.path.dirname(
  21.     os.path.dirname(
  22.         os.path.dirname(os.path.abspath(__file__)))), 'resolver')
  23.  
  24. sys.path.append(accessorPath)
  25.  
  26. # noinspection PyUnresolvedReferences
  27. import frostburnS3Accessor as fbS3
  28.  
  29.  
  30. def async(fn):
  31.     """
  32.    This decorator method allows for running *fn* asynchronously.
  33.    :param fn:
  34.    :return:
  35.    """
  36.  
  37.     def wrapper(*args, **kwargs):
  38.         thread = threading.Thread(target=fn, args=args, kwargs=kwargs)
  39.         thread.start()
  40.         thread.join()
  41.     return wrapper
  42.  
  43.  
  44. class DeleteComponentsFromLocationAction(ftrack.Action):
  45.     """
  46.    This ``ftrack.Action`` subclass will allow the user to download all
  47.    components from an entity.
  48.    """
  49.  
  50.     # Action identifier
  51.     identifier = 'delete-components-from-location'
  52.  
  53.     # Action label
  54.     label = 'Delete Component(s)'
  55.  
  56.     # Current user from OS environment
  57.     currentUser = getpass.getuser()
  58.  
  59.     def __init__( self ):
  60.         super( DeleteComponentsFromLocationAction, self ).__init__( )
  61.  
  62.         self.componentsToDelete = None
  63.  
  64.  
  65.     def validateRole(self, user, roleToCheck=None):
  66.         """
  67.        This method checks the user permissions.
  68.        Returns ``True`` if user has specified ``ftrack.Role``. If no roleToCheck
  69.        is specified, will return a ``list`` of all ``ftrack.Role``s that the user has.
  70.  
  71.        :param user: ``str`` that is the username.
  72.        :param roleToCheck: ``str`` or ``ftrack.Role`` or ``tuple`` of roles
  73.                        as strings to check against, or ``None`` to return all roles.
  74.        :return: ``True`` if user has roleToCheck, ``None`` if user does not.
  75.        :rtype: ``True`` or ``None`` or ``ftrack.Role``
  76.        """
  77.  
  78.         ftrackUser = ftrack.User(user)
  79.  
  80.         # get all roles this user has
  81.         userRoles = ftrackUser.getRoles()
  82.  
  83.         def checkAgainstRole(userRolesToCheck, rolesToCheck):
  84.             if isinstance(rolesToCheck, str):
  85.                 rolesToCheck = [rolesToCheck]
  86.  
  87.             for r in userRolesToCheck:
  88.                 if r.getName() in rolesToCheck:
  89.                     return True
  90.  
  91.         # return list of ftrack Roles if no roleToCheck was specified to check against
  92.         if not roleToCheck:
  93.             return userRoles
  94.  
  95.         else:
  96.             # Check if user has specified roleToCheck
  97.             if isinstance(roleToCheck, ftrack.Role):
  98.                 roleToCheck = roleToCheck.getName()
  99.  
  100.             result = checkAgainstRole(userRoles, roleToCheck)
  101.  
  102.             if result:
  103.                 return True
  104.             else:
  105.                 return None
  106.  
  107.  
  108.     def validateSelection(self, selection):
  109.         """
  110.        This method validates the user-selected entities in ftrack UI.
  111.        Returns if *selection* is valid.
  112.        Ensures that only Asset Versions, Task View and Show tab are valid selections.
  113.        :param selection:
  114.        :return:
  115.        """
  116.  
  117.         if len(selection) >= 1 and \
  118.             any(
  119.                 True for item in selection if
  120.                 item.get('entityType') in ('assetversion', 'task', 'show')
  121.             ):
  122.             return True
  123.  
  124.         else:
  125.             return False
  126.  
  127.  
  128.     def discover(self, event):
  129.         """
  130.        This method is invoked when the user brings up the Actions menu from the
  131.        web UI. This action will only return the config when triggered on a single
  132.        ``ftrack.AssetVersion`` object.
  133.        """
  134.  
  135.         data = event['data']
  136.  
  137.         selection = data.get('selection', [])
  138.  
  139.         # validate selection
  140.         if not self.validateSelection(selection):
  141.             return
  142.  
  143.         # validate the user role
  144.         if not self.validateRole(user=self.currentUser,
  145.                                  roleToCheck=('Administrator', 'Project Manager', 'API')):
  146.             return
  147.  
  148.         self.logger.info('Got selection: {0}'.format(selection))
  149.  
  150.         # Return data to show in web UI
  151.         return super(DeleteComponentsFromLocationAction, self).discover(event)
  152.  
  153.  
  154.     def launch( self, event ):
  155.         """
  156.        This method is invoked when the user executes the Action. This brings up
  157.        a UI to choose where to save the components to.
  158.        """
  159.  
  160.         # This returns a dictionary containing the data returned from the server.
  161.         data = event['data']
  162.  
  163.         # Get the entity that was selected
  164.         selection = data.get('selection', [])
  165.         userId = event['source']['user']['id']
  166.  
  167.         # If user has executed action (since values have been returned)
  168.         if 'values' in data:
  169.  
  170.             # Delete the components
  171.             location = ftrack.Location(event['data']['values']['location'])
  172.             self.deleteComponentsFromLocation(componentsToDelete=self.componentsToDelete,
  173.                                               location=location,
  174.                                               userId=userId)
  175.  
  176.             # Publish message to ftrack UI
  177.             return {
  178.                 'success': True,
  179.                 'message': 'Deleting components...'
  180.             }
  181.  
  182.         componentData = self.getComponentData(selection=selection, userId=userId)
  183.  
  184.         if componentData:
  185.             components, locations = componentData
  186.             self.componentsToDelete = components
  187.  
  188.         else:
  189.             components = []
  190.             locations = []
  191.  
  192.         # Format dict to be used in the Custom Actions UI
  193.         uiItems = {
  194.             'items' : [
  195.                 {
  196.                     'value' : '## Delete Components from Location ##',
  197.                     'type' : 'label'
  198.                 },
  199.                 {
  200.                     'value' : 'No. of Components on selection: {0}'
  201.                         .format(len(components)),
  202.                     'type' : 'label'
  203.                 }
  204.             ]
  205.         }
  206.  
  207.         if locations:
  208.             locationEnumUiItems = [
  209.                 {
  210.                     'label' : location.getName(),
  211.                     'value' : location.getId()
  212.                 }
  213.                 for location in locations
  214.             ]
  215.  
  216.             locationUiItem = {
  217.                 'label' : 'Location to remove from',
  218.                 'type' : 'enumerator',
  219.                 'name' : 'location',
  220.                 'value' : locationEnumUiItems[0]['value'],
  221.                 'data' : locationEnumUiItems
  222.             }
  223.  
  224.             uiItems['items'].append(locationUiItem)
  225.  
  226.         # return dict with formatted data for displaying the custom actions ui
  227.         return uiItems
  228.  
  229.  
  230.     def register(self):
  231.         """
  232.        This overloads the base register() function to only subscribe to receive
  233.        certain events that have user information in them.
  234.  
  235.        Used to filter on users that initiated the action.
  236.        """
  237.  
  238.         self.logger.debug('register() called from: {0}'.format(__name__))
  239.  
  240.         # only subscribe/launch if this action's username match.
  241.         ftrack.EVENT_HUB.subscribe(
  242.             subscription='topic=ftrack.action.discover and source.user.username={0}'
  243.                 .format(self.currentUser),
  244.             callback=self.discover
  245.         )
  246.  
  247.         ftrack.EVENT_HUB.subscribe(
  248.             subscription='topic=ftrack.action.launch and data.actionIdentifier={0} '
  249.                          'and source.user.username={1}'
  250.                 .format(self.identifier, self.currentUser),
  251.             callback=self.launch
  252.         )
  253.  
  254.  
  255.     def getVersionsInSelection(self, selection):
  256.         """
  257.        This method returns a list of the ``ftrack.AssetVersion`` objects that
  258.        are associated with a *selection*.
  259.  
  260.        :param selection: ``list`` containing currently selected ftrack entities.
  261.        :return: ``list`` containing ``ftrack.AssetVersion`` objects.
  262.        :rtype: ``list``
  263.        """
  264.  
  265.         versions = []
  266.  
  267.         # Iterate over selection and get the available versions
  268.         for item in selection:
  269.             self.logger.info(
  270.                 'Looking for versions on entity ({0}, {1})'
  271.                     .format(item['entityId'], item['entityType'])
  272.             )
  273.  
  274.             # handle Asset version entities
  275.             if item['entityType'] == 'assetversion':
  276.                 versions.append(ftrack.AssetVersion(item['entityId']))
  277.                 continue
  278.  
  279.             # Get a valid entity type
  280.             entity = None
  281.  
  282.             # Handle selection from the show tab
  283.             if item['entityType'] == 'show':
  284.                 entity = ftrack.Project(item['entityId'])
  285.  
  286.             # Handle Task entities
  287.             elif item['entityType'] == 'task':
  288.                 entity = ftrack.Task(item['entityId'])
  289.  
  290.             if not entity:
  291.                 continue
  292.  
  293.             assets = entity.getAssets(includeChildren=True)
  294.             self.logger.info('Found {0} assets on entity'.format(len(assets)))
  295.  
  296.             # Grab each AssetVersion from each Asset
  297.             for asset in assets:
  298.                 assetVersions = asset.getVersions()
  299.                 self.logger.info(
  300.                     'Found {0} versions on asset {1}'.format(len(assetVersions), asset.getId())
  301.                 )
  302.                 versions.extend(assetVersions)
  303.  
  304.         self.logger.info('Found {0} versions in selection'.format(len(versions)))
  305.         return versions
  306.  
  307.  
  308.     def getLocationsOfComponents(self, components):
  309.         """
  310.        This method will return a list of all ``ftrack.Location``s that are found
  311.        associated with a ``list`` of ``ftrack.Component``s.
  312.        :param components: ``list`` containing ``ftrack.Components``
  313.        :return: ``list`` containing ``ftrack.Location`` objects
  314.        :rtype: ``list``
  315.        """
  316.  
  317.         validLocations = []
  318.  
  319.         # get all possible locations for component to exist in
  320.         locations = ftrack.getLocations(includeHidden=True, excludeInaccessible=True)
  321.  
  322.         if locations:
  323.             # for each component, check valid locations that it exists in
  324.             for component in components:
  325.                 assert isinstance(component, ftrack.Component), \
  326.                     '### {0} is not a ftrack.Component object!!!'.format(component)
  327.  
  328.                 for location in locations:
  329.                     try:
  330.                         component.switchLocation(location)
  331.                         self.logger.debug('{0} exists in: {1}'.format(component, location))
  332.                         validLocations.append(location)
  333.  
  334.                     except ftrack.ComponentNotInAnyLocationError:
  335.                         self.logger.warning('# {0} does not exist in any Location!'
  336.                                             .format(component))
  337.                     except ftrack.LocationError:
  338.                         self.logger.debug('{0} does not exist in: {1}'
  339.                                           .format(component, location))
  340.  
  341.         return validLocations
  342.  
  343.  
  344.     def getComponentData(self, selection, userId, location='auto'):
  345.         """
  346.        This method returns a list of the ``ftrack.Component`` objects that
  347.        are associated with a *selection*.
  348.  
  349.        :param selection: ``list`` containing currently selected ftrack entities.
  350.        :param location: ``ftrack.Location`` object that Component should be switched to.
  351.                        use 'auto' to get first Location with 100% availability.
  352.        :return: ``list`` containing ftrack.Component objects.
  353.        :rtype: ``list``
  354.        """
  355.  
  356.         # Create ftrack API job visible in the UI
  357.         job = ftrack.createJob('Delete Components (Gathering data...)', 'running', user=userId)
  358.  
  359.         try:
  360.             versions = self.getVersionsInSelection(selection)
  361.  
  362.             # Create list to append all found component objects to
  363.             components = []
  364.  
  365.             for version in versions:
  366.                 self.logger.info('Looking for components on version {0}'.format(version.getId()))
  367.                 try:
  368.                     components.extend(version.getComponents(location=location))
  369.  
  370.                 # Possible to trigger if Component is uploaded to different location (ftrackreview)
  371.                 except ftrack.ComponentNotInLocationError:
  372.                     self.logger.warning('# Not all Components were able to be found in: {0}'
  373.                                         .format(location))
  374.  
  375.                     # Only add components that are available in the specified location instance
  376.                     componentList = version.getComponents(location='auto')
  377.  
  378.                     # todo: handle components from different locations such as ftrack.review
  379.                     for component in componentList:
  380.                         try:
  381.                             component.switchLocation(location)
  382.                             components.append(component)
  383.  
  384.                         except ftrack.ComponentNotInAnyLocationError:
  385.                             self.logger.warning('# {0} was skipped because it does not exist in any Location!'
  386.                                 .format(component.getName()))
  387.  
  388.                         except ftrack.LocationError:
  389.                             self.logger.warning('# {0} was skipped because it does not exist in {1}!'
  390.                                 .format(component.getName(), location))
  391.  
  392.             self.logger.info('Found {0} components in selection'.format(len(components)))
  393.  
  394.             locations = self.getLocationsOfComponents(components)
  395.  
  396.             # filter out all NoneType objects
  397.             locations = list(set([location for location in locations
  398.                          if isinstance(location, ftrack.Location)]))
  399.  
  400.         except Exception:
  401.             job.setStatus('failed')
  402.             self.logger.exception(traceback.print_exc())
  403.             raise RuntimeError
  404.  
  405.         # Finish API job
  406.         job.setStatus('done')
  407.  
  408.         return components, locations
  409.  
  410.  
  411.     @async
  412.     def deleteComponentsFromLocation(self, componentsToDelete, location, userId):
  413.         """
  414.        This method deletes the components from the specified ``ftrack.Location`` instance.
  415.        This is run asynchronously in order to ensure that the UI is responded to.
  416.        """
  417.  
  418.         self.logger.debug('Deleting {0} from: {1}'.format(componentsToDelete, location))
  419.  
  420.         assert isinstance(location, ftrack.Location), \
  421.             '### {0} is not a valid ftrack Location instance!!!'
  422.  
  423.         # Create ftrack API job visible in the UI
  424.         job = ftrack.createJob('Delete Components (Deleting components...)', 'running', user=userId)
  425.  
  426.         try:
  427.             numberOfComponents = len(componentsToDelete)
  428.  
  429.             self.logger.info('Deleting {0} components...'.format(numberOfComponents))
  430.  
  431.             # For each Component, remove it from location
  432.             for idx, component in enumerate(componentsToDelete, start = 1):
  433.  
  434.                 self.logger.info('Deleting component {0} of {1}...'.format(idx, numberOfComponents))
  435.                 job.setDescription('Deleting component {0} of {1}...'.format(idx, numberOfComponents))
  436.  
  437.                 assert isinstance(component, ftrack.Component), \
  438.                     '### {0} is not a valid ftrack Component object!!!'.format(component)
  439.  
  440.                 # switch location first to ensure it will be deleted correctly
  441.                 component.switchLocation(location)
  442.  
  443.                 # Delete the component
  444.                 location.removeComponent(component)
  445.  
  446.             self.logger.info('Components deleted!')
  447.             job.setStatus('done')
  448.  
  449.  
  450.         except Exception:
  451.             job.setStatus('failed')
  452.  
  453.  
  454. def register(registry, **kwargs):
  455.     """
  456.    Register action. Called when used as an event plugin.
  457.    :param registry:
  458.    :param kwargs:
  459.    :return:
  460.    """
  461.  
  462.     logger = logging.getLogger(__name__)
  463.  
  464.     logger.debug('Registering action: {0}'.format(
  465.         os.path.abspath(os.path.dirname(__file__))))
  466.  
  467.     action = DeleteComponentsFromLocationAction()
  468.     action.register()
  469.  
  470.  
  471. def main(arguments=None):
  472.     """
  473.    This entry point sets up logging and registers action when this is run
  474.    as a standalone action.
  475.    :param arguments:
  476.    :return:
  477.    """
  478.     if arguments is None:
  479.         arguments = []
  480.  
  481.     parser = argparse.ArgumentParser()
  482.     # Allow setting of logging level from arguments.
  483.     loggingLevels = {}
  484.     for level in (
  485.         logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
  486.         logging.ERROR, logging.CRITICAL
  487.     ):
  488.         loggingLevels[logging.getLevelName(level).lower()] = level
  489.  
  490.     parser.add_argument(
  491.         '-v', '--verbosity',
  492.         help='Set the logging output verbosity.',
  493.         choices=loggingLevels.keys(),
  494.         default='info'
  495.     )
  496.     namespace = parser.parse_args(arguments)
  497.  
  498.     # Set up basic logging
  499.     logging.basicConfig(level=loggingLevels[namespace.verbosity])
  500.  
  501.     # Subscribe to action.
  502.     ftrack.setup()
  503.     action = DeleteComponentsFromLocationAction()
  504.     action.register()
  505.  
  506.     # Wait for events
  507.     ftrack.EVENT_HUB.wait()
  508.  
  509.  
  510. if __name__ == '__main__':
  511.     raise SystemExit(main(sys.argv[1:]))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement