Source code for fcsxml

#!/usr/bin/python

################################
##
##  Transmogrifier: fcsxml
##  A Final Cut Server import/export tool 
##
##  Written by: Beau Hunter beau@318.com
##  318 Inc 05/2009
##  
##  This class provides the primary interface for reading and interpreting FCS 
##  generated XML. This class provides primary  methods which assist in the
##  conversion of FCS XML and media files to a third party format. 
##
##  This code is made available via the GPL2 license as part of the Transmogrifier
##  project available at:
##  http://sourceforge.net/projects/transmogrifier/
##
#############################################################


import sys,os.path,shutil,subprocess
import re,datetime,time,tempfile,copy
import urllib, plistlib
import codecs
from ConfigParser import *
from xml.dom import minidom


version = '.96beta'
build = '2011040701'

[docs]class FCSBaseObject: '''Our base object. Provides basic error logging capabilities''' ## Logging vars log = [] lastError = "" lastMSG = "" debug = False keepFiles = False isError = False logOffset = 1 printLogs = False printLogDate = True printClassInLog = False timezoneOffset = 5 def __init__(self): '''Our construct''' self.log = [] self.lastError = '' self.lastMSG = '' self.debug = False self.keepFiles = False self.isError = False self.logOffset = 1 self.printLogs = False self.printLogDate = True self.printClassInLog = False
[docs] def entityPath(self): '''Returns the entityPath''' if self.entityType and self.entityID: entityPath = '/%s/%s' % (self.entityType,self.entityID) else: self.logger('Could not determine entityPath, entityType or entityID not set!','error') return False return entityPath
[docs] def logger(self, logMSG, logLevel='normal',printClassInLog='unset'): '''(very) Basic Logging Function, we'll probably migrate to msg module''' if (logLevel == 'error' or logLevel == 'normal' or logLevel == 'detailed' or self.debug): i = 0 headerText = '' while i < self.logOffset: headerText+=' ' i+=1 if logLevel.lower() == 'error': headerText = ' ERROR : %s' % headerText elif logLevel.lower() == 'debug': headerText = ' DEBUG : %s' % headerText elif logLevel.lower() == 'warning': headerText = 'WARNING : %s' % headerText elif logLevel.lower() == 'detailed': headerText = ' DETAIL : %s' % headerText else: headerText = ' INFO : %s' % headerText if self.printLogDate: dateString = datetime.datetime.today().strftime("%b %d %H:%M:%S") headerText = "%s: %s" % (dateString,headerText) if self.printLogs: if printClassInLog == 'unset': printClassInLog = self.printClassInLog if printClassInLog: print '%s%s: %s' % (headerText,self.__class__.__name__,logMSG) else: print '%s%s' % (headerText,logMSG) sys.stdout.flush() self.lastMSG = logMSG if logLevel == 'error': self.lastError = logMSG self.log.append({'logLevel' : logLevel, 'logMSG' : logMSG})
[docs] def logs(self,logLevel=''): '''Returns an array of logs matching logLevel''' returnedLogs = [] logs = self.log for log in logs: if logLevel and logLevel.lower() == log['logLevel'].lower(): returnedLogs.append(log)
[docs] def lastError(self): '''Returns last error''' errorLogs = self.logs('error') return errorLogs[len(errorLogs)]
[docs]class FCSXMLField(FCSBaseObject): '''Our FCSXMLField object, used to store value, field name, and db name''' name = '' dbname = '' value = '' dataType = '' dbDataType = '' dataTypeLoaded = False valueLoaded = False validDataTypes = { 'string' : 'KtString32','string2' : 'KtString','int':'KtInt', 'integer':'KtInt','int64':'KtInt64','bigint':'KtInt64','dateTime':'KtDateTime', 'timestamp':'KtDateTime','timecode':'PxTimecode','float':'KtReal','coords':'KtIntXY', 'fraction':'KtFraction','bool':'KtBool','list':'KtMdValueMap' } def __init__(self, name='', value='', dataType='',dbname='',dbDataType=''): '''Our construct, dbDataType overrides dataType''' FCSBaseObject.__init__(self) self.name = name self.dbname = dbname self.dataType = '' self.dbDataType = '' if dataType: self.setDataType(dataType) if dbDataType: self.setDBDataType(dbDataType) self.setValue(value) self.dataTypeLoaded = False self.valueLoaded = False self.debug = False def __str__(self): '''Output our field name''' return "FCS Field: %s" % self.name
[docs] def setDataType(self,dataType): '''Method to set dataType''' if dataType in self.validDataTypes: if dataType == 'integer': dataType = 'int' elif dataType == 'timestamp': dataType = 'dateTime' self.dataType = dataType self.dbDataType = self.validDataTypes[dataType] self.dataTypeLoaded = True else: raise RuntimeError('dataType:%s is not defined!' % dataType)
[docs] def setDBDataType(self,dbDataType): '''Method to set dbDataType''' dataType = '' for key,value in self.validDataTypes.iteritems(): if value == dbDataType: dataType = key if dataType: self.dataType = dataType self.dbDataType = dbDataType self.dataTypeLoaded = True else: if dbDataType == 'KtAtom' or dbDataType == 'KtAtomList': print 'Fields with type: %s are not currently supported!' % dbDataType return False else: raise RuntimeError('dbDataType:%s is not defined!' % dbDataType)
[docs] def setValue(self,newValue): '''Method to set internal value, performs validation and conformity''' dataType = self.dataType myDateTime = '' ## String can be either string or string2 if dataType[0:6] == 'string': self.value = '%s' % newValue elif dataType == 'int64': if newValue: self.value = int(newValue) else: self.value = 0 elif dataType == 'dateTime': inDateTimeFormats = ['%Y-%m-%d %H:%M:%S+%f','%Y-%m-%dT%H:%M:%SZ'] outDateTimeFormat = '%Y-%m-%dT%H:%M:%SZ' if newValue.__class__.__name__ == 'datetime': myDateTime = newValue else: count = 0 while count < len(inDateTimeFormats) and not myDateTime: self.logger('Testing Pattern: \'%s\' against string: \'%s\'' % (inDateTimeFormats[count],newValue),'debug') try: myDateTime = datetime.datetime.strptime('%s' % newValue,inDateTimeFormats[count]) except: pass count += 1 ## Account for timezone changes (FCS stores timestamps in UTC) if myDateTime: myDateTime = myDateTime + datetime.timedelta(hours=self.timezoneOffset) self.value = myDateTime.strftime(outDateTimeFormat) elif dataType == 'bool': ##self.logger("setValue() BOOL FIELD:%s HIT WITH VALUE: %s " % (self.name,newValue),'debug') if (type('') == type(newValue) or type(u'') == type(newValue)): if (newValue.lower() == 'true'): self.value = True else: self.value = False elif type(1) == type(newValue): if newValue > 0: self.value = True else: self.value = False elif type(True) == type(newValue): self.value = newValue else: self.logger("setValue() ACK!! ERRORRRRRz: given type:%s" % type(newValue),'debug') validationInfo = ('BOOL Field must be provided a boolean value, an ' 'integer value (0 or 1) or a string value (\'true\' or \'false\')') raise FCSValidationError(fieldName=self.name, value=newValue, dataType=self.dataType, validationInfo=validationInfo) self.logger("setValue() - USING VALUE: %s " % self.value,'debug') else: self.value = '%s' % newValue self.valueLoaded = True return True
[docs] def printInfo(self): '''output basic field info''' print 'FieldName: %s\n Value: %s\n Type: %s\n dbname: %s\n' % (self.name,self.value,self.dataType,self.dbname)
[docs]class FCSXMLObject(FCSBaseObject): entityID = 0 entityType = 'asset' path = '' fcsXML = '' fields = {} deviceDict = {} lastError = '' log = [] overwriteExistingFiles = True debug = False def __init__(self,entityID=0): '''construct function, can be called with a specified integer id''' FCSBaseObject.__init__(self) self.entityID = entityID self.entityType = 'asset' self.path = '' self.fcsXML = '' self.fields = {} self.deviceDict = {} self.lastError = '' self.log = []
[docs] def setFile(self,filePath): '''Historical method, use loadFromFile()''' return self.loadFromFile(filePath)
[docs] def loadFromFile(self,filePath): '''import file''' filePath = os.path.abspath(os.path.realpath(os.path.expanduser(filePath))) ## todo: get entityType if os.path.exists(filePath): self.path = filePath self.fcsXML = minidom.parse(filePath).documentElement self.logger('Loading XML from File: %s' % filePath, 'info') return self.loadXML() else: self.logger('File does not exist at path: %s, exiting!' % filePath, 'error')
def printLogs(self, logLevel='all'): for log in self.log: if logLevel == 'all' or logLevel == log['logLevel']: print 'fcsvr_xml:%s:%s' % (log['logLevel'], log['logMSG'])
[docs] def getXMLNodeText(self, nodes): '''returns text value for passed nodes''' text = '' for node in nodes: if node.nodeType == node.TEXT_NODE: text = text + node.data return text
[docs] def loadFromFCSVRClient(self,fcsvrClient): '''Loads our object from values stored in fcsvrClient''' try: self.entityID = fcsvrClient.entityID self.entityType = fcsvrClient.entityType self.fields = fcsvrClient.fields except: self.logger('An error occured loading from the object','error') return False return True
[docs] def loadXMLFromString(self,xmlString=''): '''loads xml in from passed string''' if xmlString: try: self.fcsXML = minidom.parseString(xmlString).documentElement except: self.logger('An error occurred reading in xmlString, could not create' + ' minidom object!','error') self.logger("\n######## XML String ######\n'%s'\n###### END XML String #####" % xmlString,'debug') return False else: self.logger('loadXMLFromString() failed! Provided string is empty!','error') return False return self.loadXML()
[docs] def loadXML(self, xml=''): '''loads values from stored xml''' self.logger('Loading XML!','debug') if not xml: if not self.fcsXML: self.logger('loadXML() XML could not be loaded!', 'error') return False else: xml = self.fcsXML try: entity = xml.getElementsByTagName('entity')[0] self.entityType = entity.attributes['entityType'].value self.entityID = re.sub(r'\/asset\/(\d*?)',r'\1',entity.attributes['entityId'].value) except: ##self.logger('Error reading XML format, attempting to read FCS WriteXML format','warning') #try: entity = xml.getElementsByTagName('params')[0] requestEntity = xml.getElementsByTagName('request')[0] self.entityType = re.sub(r'\/(.*)\/(.*)',r'\1',requestEntity.attributes['entityId'].value) self.entityID = re.sub(r'\/(.*)\/(.*)',r'\2',requestEntity.attributes['entityId'].value) #except: # self.logger('Failed reading FCS WriteXML format, cannot read XML','error') # return False fields = entity.getElementsByTagName('mdValue') for field in fields: if len(field.childNodes) > 0: theField = FCSXMLField(name=field.attributes['fieldName'].value, value=self.getXMLNodeText(field.childNodes), dataType=field.attributes['dataType'].value) else: theField = FCSXMLField(name=field.attributes['fieldName'].value, value='', dataType=field.attributes['dataType'].value) self.fields[field.attributes['fieldName'].value] = theField ##self.lastError = 'Loaded Key:', theField.name return True
[docs] def valueForField(self, fieldName): '''return value for the passed field name''' if not fieldName in self.fields: self.logger('valueForField() No Field with key: %s exists!' % fieldName, 'warning') raise FCSFieldNotFoundError(fieldName) else: theField = self.fields[fieldName] if theField.dataType == 'timecode': ## return timecode in seconds tempValue = theField.value.replace( '.', ':' ) tempValue = tempValue.replace( ';', ':' ) tempValue = tempValue.replace(',',':') tempValueArray = tempValue.split(':') hours = int(tempValueArray[0]) minutes = int(tempValueArray[1]) seconds = int(tempValueArray[2]) frames = int(tempValueArray[3]) ## need to extrapolate framerate to get seconds-per-frame frameRate = float(self.valueForField('Video Frame Rate')) if frameRate: frameSeconds = (frames/frameRate) totalSeconds = (hours * 60 * 60) + (minutes * 60) + seconds + frameSeconds return totalSeconds else: return theField.value
[docs] def dataTypeForField(self, fieldName): '''returns datatype for the passed field name''' if not fieldName in self.fields: self.logger('dataTypeForField() No Field with key: %s exists!' % fieldName, 'warning') return False else: theField = self.fields[fieldName] if not theField.dataType: theField.dataType = 'string' return theField.dataType
[docs] def fieldWithName(self, fieldName): '''returns FCSXMLField object for the passed field name''' if not fieldName in self.fields: self.logger('fieldWithName() No Field with key: %s exists!' % fieldName, 'warning') return False else: theField = self.fields[fieldName] return theField
[docs] def setField(self, field): '''Register FCSXMLField, replaces existing field object with same name''' if field.__class__.__name__ != 'FCSXMLField': self.logger('setField() Passed invalid data! Expects FCSXMLField', 'error') return False else: self.fields[field.name] = field
[docs] def appendField(self, field): '''Append FCSXMLField, fails if field object with same name already exists''' if field.__class__.__name__ != 'FCSXMLField': self.logger('appendField() Passed invalid data! Expects FCSXMLField', 'error') return False if field.name in self.fields: self.logger('appendField() Field with name: %s already exists!' % field.name, 'error') return False else: self.fields[field.name] = field
[docs] def removeFieldWithName(self, fieldName): '''Remove FCSXMLField object registered with passed field name''' if fieldName.__class__.__name__ == 'FCSXMLField': fieldName = fieldName.name if fieldName in self.fields: del self.fields[fieldName] self.logger('removeFieldWithName() Field with name: %s removed!' % fieldName, 'info') return True else: return False
[docs] def xmlOut(self, filePath=''): '''Write our XML out to a file''' if not self.entityID > 0: self.logger('xmlOut() entityID not set! Cannot generate XML.', 'warning') if not len(self.fields) > 0: self.logger('xmlOut() no fields set! Cannot generate XML.', 'error') return False if (filePath and (not os.path.exists(filePath) \ or (os.path.exists(filePath) and self.overwriteExistingFiles)) and os.path.isdir(os.path.dirname(filePath))) \ or not filePath : ## create our new xml doc, add our root FCS elements: ## <?xml version="1.0"?> ## <FinalCutServer> ## <getMdReply> ## <entity entityType="asset" entityId="/asset/587344"> ## <metadata> xmlDoc = minidom.Document() fcsElement = xmlDoc.createElement('FinalCutServer') xmlDoc.appendChild(fcsElement) requestElement = xmlDoc.createElement('request') requestElement.setAttribute('entityId', '/%s/%s' % (self.entityType,self.entityID)) requestElement.setAttribute('reqId', 'setMd') fcsElement.appendChild(requestElement) paramsElement = xmlDoc.createElement('params') requestElement.appendChild(paramsElement) ## And then our individual fields. for field in self.fields.itervalues(): theFieldElement = xmlDoc.createElement('mdValue') theFieldElement.setAttribute('fieldName', field.name) theFieldElement.setAttribute('dataType', field.dataType) if field.dataType == 'bool': if field.value: fieldValue = 'True' else: fieldValue = 'False' else: fieldValue = field.value if fieldValue: theValueNode = xmlDoc.createTextNode(fieldValue) theFieldElement.appendChild(theValueNode) else: theValueNode = '' ## Append our field element to our 'params' element i.e. ## <params> ## <mdValue fieldName="Size" dataType="int64">3798309</mdValue> paramsElement.appendChild(theFieldElement) del theFieldElement if filePath: theFile = open(filePath, 'w') xmlDoc.writexml(theFile) theFile.close() else: return xmlDoc.toxml() ##return xmlDoc.toprettyxml() elif os.path.exists(filePath) and not self.overwriteExistingFiles: self.logger('xmlOut() File already exists at path: %s, exiting!' % filePath, 'error') return False elif not os.path.exists(os.path.dirname(filePath)): self.logger('xmlOut() Directory does not exist at path: %s, exiting!' % os.path.dirname(filePath), 'error') return False else: self.logger('xmlOut() Unknown error writing XML', 'error') return False xmlDoc.unlink() return True
[docs]class FCSVRClient(FCSBaseObject): '''Our FCSVRClient object, for manipulating FCS via fcsvr_client''' entityType = '' entityID = '' entityMetadataSet = '' entityTitle = '' defaultProductionMetadataSet = 'pa_production_package' defaultAssetMetadataSet = 'pa_asset_media' defaultThumbnailMetadataSet = 'pa_asset_thumbnail' fields = {} deviceDict = {} supportDir = '/tmp' overwriteExistingFiles = True thumbnailPath = '' posterFramePath = '' proxyPath = '' editProxyPath = '' thumbnailDeviceName = '' parentXMLObject = '' childXMLObject = '' FCSUID = 0 ## Our FCS User ID. If this is not set we will attempt ## to read it in from /Library/Preferences/com.apple.FinalCutServer.settings.plist useSudo = False ## Call /usr/bin/sudo before calling fcsvr_client commands ## that require root access. You'll want to make sure that ## You have modified your sudoers file if you set this to true. pathToFCSVRClient = '/Library/Application Support/Final Cut Server/Final Cut Server.bundle/Contents/MacOS/fcsvr_client' devicesMap = '' registeredEntities = ['asset','project','dev','field','mdgroup','group'] def __init__(self,entityType='asset',id='',entityPath='',cfgParser=''): '''Our constructor''' FCSBaseObject.__init__(self) if entityPath: pathMatch = re.match('\/(.*)\/(.*)',entityPath) if pathMatch: self.entityType = pathMatch.groups()[0] self.entityID = pathMatch.groups()[1] else: self.entityType = entityType self.entityID = id self.entityMetadataSet = '' self.overwriteExistingFiles = True self.thumbnailPath = '' self.parentXMLObject = '' self.childXMLObject = '' self.thumbnailDeviceName = 'Library' self.debug = False self.keepFiles = False self.devicesMap = '' self.fields = {} self.deviceDict = {} self.FCSUID = self.getFCSUID() self.thumbnailPath = '' self.posterFramePath = '' self.proxyPath = '' self.editProxyPath = '' self.defaultProductionMetadataSet = 'pa_production_package' self.defaultAssetMetadataSet = 'pa_asset_media' self.useSudo = True self.printClassInLog = True self.debug = False if cfgParser: self.loadConfiguration(cfgParser) if id and entityType == 'asset': self.initWithAssetID(assetID=id)
[docs] def loadConfiguration(self, parser): '''Load from configuration file, expects a ConfigParser type object. If you subclass, you should call this function. If we return false then you should abort. or do your own sanity checks''' if not isinstance(parser,ConfigParser): self.logger('loadConfiguration() Not passed a valid ConfigParser Object!', 'error') return False try: self.configParser = parser ## Get Debug status, first from FCSVRClient section, then from global try: self.printLogs = parser.getboolean('FCSVRClient','printLogs') except: try: self.printLogs = parser.getboolean('GLOBAL','printLogs') except: pass try: self.debug = parser.getboolean('FCSVRClient','debug') except: try: self.debug = parser.getboolean('GLOBAL','debug') except: pass try: self.keepFiles = parser.getboolean('FCSVRClient','keepFiles') except: try: self.keepFiles = parser.getboolean('GLOBAL','keepFiles') except: pass try: self.defaultProductionMetadataSet = parser.get('FCSVRClient','defaultproductionmdset') except: pass try: self.defaultAssetMetadataSet = parser.get('FCSVRClient','defaultassetmdset') except: pass try: self.defaultThumbnailMetadataSet = parser.get('FCSVRClient','defaultthumbnailmdset') except: pass try: self.useSudo = parser.getboolean('FCSVRClient','usesudo') except: pass except: self.logger('loadConfiguration() Problem loading configuration records, please double check your configuration', 'error') return True
[docs] def getFCSUID(self): '''Determine the UID of the running FCS User, this is stored in /Library/Preferences/com.apple.FinalCutServer.settings.plist''' plistFile = '/Library/Preferences/com.apple.FinalCutServer.settings.plist' UID = 0 if self.FCSUID: return self.FCSUID plistDict = {} if os.path.isfile(plistFile): try: plistObj = plistlib.readPlist(plistFile) except: self.logger('loadFromFile() Error Reading File!','error') return False if 'USER_ID' in plistObj: self.FCSUID = plistObj['USER_ID'] return self.FCSUID
[docs] def getDevicesMap(self,useCache=True): '''Load device information from FCS''' if self.devicesMap and len(self.devicesMap) > 0 and useCache: return self.devicesMap ## Run our fcsvr_client command. fcsvrCMDTXT = "'%s' search /dev --xml" % (self.pathToFCSVRClient) fcsvrCMD = subprocess.Popen(fcsvrCMDTXT,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() if not fcsvrCMD.returncode == 0: ##self.logger('%s' % fcsvrCMD_STDOUT,'error') ##raise RuntimeError('Could not parse output from fcsvr_client') return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString='fcsvr_client %s' % cmdString) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client','error') raise RuntimeError('Could not parse output from fcsvr_client') devicesMap = {} for rootValues in myDom.getElementsByTagName('values'): deviceDict = {} for rootValue in rootValues.getElementsByTagName('value'): if rootValue.attributes['id'].value == 'METADATA': for values in rootValue.getElementsByTagName('values'): for value in values.getElementsByTagName('value'): valueID = value.attributes['id'].value if valueID == 'DEV_ROOT_PATH': deviceDict['DEV_ROOT_PATH'] = value.childNodes[1].childNodes[0].data deviceDict['FSPATH'] = value.childNodes[1].childNodes[0].data elif valueID == 'DEVICE_TYPE': deviceDict['DEVICE_TYPE'] = value.childNodes[1].childNodes[0].data elif valueID == 'DEVICE_NAME': deviceDict['DEVICE_NAME'] = value.childNodes[1].childNodes[0].data elif valueID == 'DEVICE_ID': deviceDict['DEVICE_ID'] = int(value.childNodes[1].childNodes[0].data) deviceDict['DESC_DEVICE_ADDRESS'] = '/dev/%s' % deviceDict['DEVICE_ID'] elif valueID == 'DEV_ARCHIVE': archiveValue = value.childNodes[1].childNodes[0].data if archiveValue == 'true': deviceDict['DEV_ARCHIVE'] = True else: deviceDict['DEV_ARCHIVE'] = False if 'DEVICE_ID' in deviceDict and deviceDict['DEVICE_ID']: devicesMap[deviceDict['DEVICE_ID']] = deviceDict self.devicesMap = devicesMap return self.devicesMap
[docs] def deviceWithID(self,id): '''Return a dictionary containing pertinent device information''' deviceIDMap = self.getDevicesMap() self.logger('deviceWithID() Called with ID: %s' % id,'debug') myID = int(id) if myID in deviceIDMap: return deviceIDMap[myID] else: raise FCSEntityNotFoundError(entityType='device',entityID=id) #self.logger('DeviceID: %s has not been registered!' % id,'error') #return False
[docs] def deviceWithName(self,id): '''Return a dictionary containing pertinent device information based on device Name''' deviceIDMap = self.getDevicesMap() self.logger('deviceWithName() Called with Name: %s' % id,'debug') if not deviceIDMap: self.logger('Could not generate deviceMap, cannot continue!','error') return False for deviceID,deviceDict in deviceIDMap.iteritems(): if 'DEVICE_NAME' in deviceDict and deviceDict['DEVICE_NAME'] == id: return deviceIDMap[deviceID] ## We are here if we found no match. raise FCSEntityNotFoundError(entityType='device',entityTitle=id)
[docs] def deviceWithPath(self,path): '''Return a dictionary containing pertinent device information based on a POSIX filesystem path''' deviceIDMap = self.getDevicesMap() myDevice = {} self.logger('deviceWithName() Called with path: %s' % path,'debug') for deviceID,deviceDict in deviceIDMap.iteritems(): if 'FSPATH' in deviceDict: subPath = path[0:len(deviceDict['FSPATH'])] self.logger('deviceWithName() comparing subPath: %s to path:%s' % (subPath,deviceDict['FSPATH']),'debug') if subPath == deviceDict['FSPATH']: myDevice = deviceDict break if myDevice: return myDevice else: raise FCSEntityNotFoundError(entityType='device',entityPath=path)
[docs] def valueForDBField(self,dbFieldName): '''Return value for the passed DB field name''' self.logger('valueForDBField() hit','debug') ## Create our FCSXMLField object myField = self.loadField(FCSXMLField(dbname=dbFieldName)) return self.valueForField(myField.name)
[docs] def valueForField(self, fieldName): '''Return value for the passed field name''' self.logger('valueForField() hit','debug') if not fieldName in self.fields: self.logger('valueForField() No Field with key:\'%s\' exists! Attempting to load' % fieldName, 'warning') if not self.loadFieldWithName(fieldName): self.logger('valueForField() Could not load field with key:\'%s\'' % fieldName, 'warning') raise FCSFieldNotFoundError(fieldName) theField = self.fields[fieldName] if theField.dataType == 'timecode': ## return timecode in seconds tempValue = theField.value.replace( '.', ':' ) tempValue = tempValue.replace( ';', ':' ) tempValue = tempValue.replace(',',':') tempValueArray = tempValue.split(':') hours = int(tempValueArray[0]) minutes = int(tempValueArray[1]) seconds = int(tempValueArray[2]) frames = int(tempValueArray[3]) ## need to extrapolate framerate to get seconds-per-frame frameRate = float(self.valueForField('Video Frame Rate')) if frameRate: frameSeconds = (frames/frameRate) totalSeconds = (hours * 60 * 60) + (minutes * 60) + seconds + frameSeconds return totalSeconds elif theField.dataType == 'bool': if theField.value: return True elif theField.value: return False elif theField.dataType == 'string': if not theField.value: theField.value = '' return theField.value else: return theField.value
[docs] def appendValueForField(self,fieldName,value,useTimestamp=False): '''Appends a value to field, concatenating with the current field value''' if not fieldName in self.fields: self.logger('valueForField() No Field with key:\'%s\' exists! ' 'Attempting to load' % fieldName, 'warning') if not self.loadFieldWithName(fieldName): self.logger('valueForField() Could not load field with key:\'%s\'' % fieldName, 'warning') theField = self.fields[fieldName] if theField.dataType == 'integer': newValue = theField.value + value else: ## Get current field and value currentValue = theField.value newValue = value ## Add a line return. if currentValue: currentValue += "\n" ## If we are set to use a timestamp, prepend it to the new value if useTimestamp: currentTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M') newValue = '%s: %s' % (currentTime,newValue) self.logger('appendValueForField() Appending timestamp to new value:%s' % (newValue),'debug') newValue = "%s%s\n" % (currentValue,newValue) theField.setValue(newValue) return
[docs] def dataTypeForField(self, field): '''Returns datatype for the passed field (FCSXMLField) object.''' dbname = field.dbname name = field.name dataType = field.dataType if not dbname and not name: ##self.logger('dataTypeForField() Passed field has no name!', 'error') raise RuntimeError('Could not determine Data Type: requested field has ' 'no name specified'); elif name: dbname = self.dbFieldNameForFieldName(name) self.logger("Loading dataType for name:'%s' dbname:'%s'" % (name,dbname), 'debug') dataType = self.dataTypeForDBFieldName(dbname) field.dataType = dataType return dataType
[docs] def dbDataTypeForField(self, field): '''returns datatype for the passed field (FCSXMLField) object.''' dbname = field.dbname name = field.name dataType = field.dataType if not dbname and not name: self.logger('dataTypeForField() Field has no name!', 'error') raise RuntimeError('Could not determine Data Type: requested field has no' ' name specified'); elif name: dbname = self.dbFieldNameForFieldName(name) elif dbname: name = self.fieldNameForField(dbname) self.logger("Loading field name for name:'%s' dbname:'%s'" % (name,dbname), 'debug') dbDataType = self.dbDataTypeForDBFieldName(dbname) field.dbDataType = dbDataType return dbDataType
[docs] def fieldWithName(self, fieldName): '''returns FCSXMLField object for the passed field name''' self.logger('fieldWithName() hit','debug') if fieldName in self.fields: theField = self.fields[fieldName] return theField else: if self.loadFieldWithName(fieldName): return self.fields[fieldName] else: self.logger("Could not Retrieving Field with name: %s" % fieldName, 'warning') return False
[docs] def loadField(self,field): '''Function which loads a field. If the field is not defined for our object, we still will look up the dataType''' self.logger('loadField() hit','debug') dbFieldName = '' fieldName = '' if field.dbname: dbFieldName = field.dbname if field.name: fieldName = field.name if not fieldName and dbFieldName: fieldName = self.fieldNameForDBFieldName(dbFieldName) elif not dbFieldName and fieldName: dbFieldName = self.dbFieldNameForFieldName(fieldName) elif fieldName and dbFieldName: testName = self.fieldNameForDBFieldName(dbFieldName) testDBName = self.dbFieldNameForFieldName(fieldName) if (not testName == fieldName or not testDBName == dbFieldName): raise RuntimeError('Ambiguous data provided, please provide either a ' 'name or dbname'); else: self.logger('loadField() No fieldname provided!','error'); raise RuntimeError('Could not load field name: no data provided'); self.logger("loadField() Loading field with name:'%s' dbname:'%s'" % (fieldName,dbFieldName),'debug') ## If we have a registered entityID, try to fetch the actual field value if self.entityID: ## Run our fcsvr_client command. fcsvrCMDTXT = "'%s' getmd /%s/%s --xml" % (self.pathToFCSVRClient, self.entityType,self.entityID) fcsvrCMD = subprocess.Popen(fcsvrCMDTXT,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("fcsvr_client command: fcsvr_client getmd /%s/%s --xml" % (self.entityType,self.entityID),'debug') if not fcsvrCMD.returncode == 0: ##self.logger("%s %s" % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR),'error') ##return False return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDTXT) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: raise self.logger("Could not parse output from fcsvr_client command: " "fcsvr_client getmd /%s/%s --xml" % (self.entityType, self.entityID),'error') return False try: for rootElement in myDom.childNodes[0].childNodes: ##self.logger("Nodename: %s" %rootElement.nodeName,'debug') if rootElement.nodeName == "values": for value in rootElement.getElementsByTagName("value"): valueID = value.attributes["id"].value ##self.logger("valueID: %s" % valueID,'debug') if valueID == dbFieldName: self.testNode = value fieldValueNode = value.childNodes[1] fieldType = fieldValueNode.nodeName try: self.logger('Found field: %s, reading value.' % dbFieldName, 'debug') fieldData = fieldValueNode.childNodes[0].data except Exception, inst: self.logger('An error occured reading value for field: %s' % inst,'error') fieldData = "" self.logger('Found field: \'%s\', with data: \'%s\'' % (dbFieldName,fieldData),'debug') FCSField = FCSXMLField(name=fieldName,value=fieldData, dataType=fieldType,dbname=dbFieldName) self.fields[fieldName] = FCSField return FCSField except: self.logger("Error extracting field:'%s' from fcsvr_client command: " "fcsvr_client getmd /%s/%s --xml" % (dbFieldName,self.entityType, self.entityID),'error') raise return False ## If we have gotten to this point, then the asset did not have a value for ## the requested field. If we can query the dataType for the field, return ## a bare FCSXMLField object dataType = self.dataTypeForDBFieldName(dbFieldName) dbDataType = self.dbDataTypeForDBFieldName(dbFieldName) self.logger('Creating new field with name:\'%s\' dbname:\'%s\'' ' dataType:\'%s\' dbDataType:%s' % (fieldName,dbFieldName,dataType,dbDataType),'debug') if (dataType): FCSField = FCSXMLField(name=fieldName,dbname=dbFieldName, dataType=dataType,dbDataType=dbDataType); self.fields[fieldName] = FCSField return FCSField return False
[docs] def loadFieldWithName(self,name='',dbname=''): '''Returns FCSField object for fieldName''' self.logger('loadFieldWithName() hit','debug') dbFieldName = '' fieldName = '' if dbname: dbFieldName = dbname if name: fieldName = name if not fieldName and dbFieldName: fieldName = self.fieldNameForDBFieldName(dbFieldName) elif not dbFieldName and fieldName: dbFieldName = self.dbFieldNameForFieldName(fieldName) elif fieldName and dbFieldName: testName = self.fieldNameForDBFieldName(dbFieldName) testDBName = self.dbFieldNameForFieldName(fieldName) if (not testName == fieldName or not testDBName == dbFieldName): raise RuntimeError('Ambiguous data provided, please provide either a' ' name or dbname'); else: self.logger('loadFieldWithName() No fieldname provided!','error'); raise RuntimeError('Could not load field name: no data provided'); self.logger("Loading field name for name:'%s' dbname:'%s'" % (fieldName,dbFieldName),'debug') ## If we have a registered entityID, try to fetch the actual field value if self.entityID: ## Run our fcsvr_client command. fcsvrCMDString = "'%s' getmd /%s/%s --xml" % (self.pathToFCSVRClient, self.entityType, self.entityID) fcsvrCMD = subprocess.Popen(fcsvrCMDString,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("fcsvr_client command: %s" % fcsvrCMDString,'debug') if not fcsvrCMD.returncode == 0: ##errorString = '%s %s' % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR) ##self.logger(errorString,'error') ##raise FCSVRClientError('%s' % errorString,cmdString) return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDTXT) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: errorString = 'Could not parse output from fcsvr_client command.' self.logger('%s fcsvr_client cmd:%s' % (errorString,fcsvrCMDString),'error') raise FCSVRClientError(errorString,fcsvrCMDString) try: for rootElement in myDom.childNodes[0].childNodes: ##self.logger("loadFieldWithName() Nodename: %s" % rootElement.nodeName,'debug') if rootElement.nodeName == "values": for value in rootElement.getElementsByTagName("value"): valueID = value.attributes["id"].value ##self.logger("loadFieldWithName() valueID: %s" % valueID,'debug') if valueID == dbFieldName: ##self.logger('loadFieldWithName() found field with name:%s' % dbFieldName,'debug') self.testNode = value fieldValueNode = value.childNodes[1] fieldType = fieldValueNode.nodeName try: fieldData = fieldValueNode.childNodes[0].data except: self.logger('loadFieldWithName() An error occured reading ' ' data for field:%s' % dbFieldName,'debug') fieldData = '' self.logger('loadFieldWithName() Found field with dbname:\'%s\'' 'value:\'%s\' type:\'%s\'' % (dbFieldName,fieldType,fieldData),'debug') FCSField = FCSXMLField(name=fieldName,value=fieldData, dataType=fieldType,dbname=dbFieldName) self.fields[fieldName] = FCSField return FCSField except: self.logger("Error extracting field:'%s' from fcsvr_client command: " "fcsvr_client getmd /%s/%s --xml" % (dbFieldName,self.entityType, self.entityID),'error') raise raise FCSVRFieldNotFound(fieldName) ## If we have gotten to this point, then the asset did not have a value for ## the requested field. If we can query the dataType for the field, return ## a bare FCSXMLField object dataType = self.dataTypeForDBFieldName(dbFieldName) dbDataType = self.dbDataTypeForDBFieldName(dbFieldName) self.logger('loadFieldWithName() Creating new field with name:\'%s\' dbname:\'%s\' ' 'dataType:\'%s\' dbDataType:%s' % (fieldName,dbFieldName,dataType,dbDataType),'debug') if (dataType): FCSField = FCSXMLField(name=fieldName,dbname=dbFieldName, dataType=dataType,dbDataType=dbDataType); self.fields[name] = FCSField return FCSField raise FCSVRFieldNotFound(fieldName)
[docs] def setField(self, field): '''Register FCSXMLField, replaces existing field object with same name''' if field.__class__.__name__ != "FCSXMLField": self.logger("setField() Passed invalid data! Expects FCSXMLField", 'error') return False fieldName = field.name dbname = field.dbname dataType = field.dataType; if not fieldName and not dbname: self.logger("setField() provided field has no fieldname or dbname!",'error') return False if not fieldName and dbname: fieldName = self.fieldNameForDBFieldName(dbname) field.name = fieldName if not dbname and fieldName: dbname = self.dbFieldNameForFieldName(fieldName) field.dbname = dbname if not dataType: self.logger('setField() loading dataType for field: %s' %field.name,'debug') dataType = self.dataTypeForField(field); field.setDataType(dataType) if not field.dataType: self.logger('setField() could not produce dataType for field: %s' % fieldName,'error'); raise RuntimeError('Could not determine datatype!'); self.fields[field.name] = field return True
[docs] def appendField(self, field): '''Append FCSXMLField, fails if field object with same name already exists''' if field.__class__.__name__ != "FCSXMLField": self.logger("appendField() Passed invalid data! Expects FCSXMLField", 'error') return False if field.name in self.fields: self.logger("appendField() Field with name: %s already exists!" % field.name, 'error') return False else: self.fields[field.name] = field
[docs] def removeFieldWithName(self, fieldName): '''Remove FCSXMLField object registered with passed field name''' if fieldName.__class__.__name__ == "FCSXMLField": fieldName = fieldName.name if fieldName in self.fields: del self.fields[fieldName] self.logger("removeFieldWithName() Field with name: %s removed!" % fieldName, "info") return True else: return False
[docs] def fieldNameForDBFieldName(self,dbname): '''Returns a field name for a dbname (FCS Read XML field name from FCSVR_CLIENT field name''' self.logger("Retrieving field name for dbname:%s" % dbname,'debug') ## Run our fcsvr_client command. fcsvrCMDTXT = "'%s' getmd /field/%s --xml" % (self.pathToFCSVRClient,dbname) fcsvrCMD = subprocess.Popen(fcsvrCMDTXT,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("fcsvr_client command: fcsvr_client getmd /field/%s --xml" % dbname,'debug') if not fcsvrCMD.returncode == 0: return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDTXT) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger("Could not parse output from fcsvr_client command: fcsvr_client getmd /field/%s --xml" % dbname,'error') return False try: fieldName = "" for rootElement in myDom.childNodes[0].childNodes: self.logger("Nodename: %s" %rootElement.nodeName,'debug') if rootElement.nodeName == "values": for value in rootElement.getElementsByTagName("value"): valueID = value.attributes["id"].value #self.logger("valueID: %s" % valueID,'debug') if valueID == "FIELD_NAME": fieldName = value.childNodes[1].childNodes[0].data break except: self.logger("Uncaught exception reading field name for dbname:%s" % dbname,'debug') return False if fieldName: self.logger("Found fieldname: %s for dbname: %s"% (fieldName,dbname),'debug') return fieldName else: return False
[docs] def dataTypeForDBFieldName(self,dbname): '''Returns a dataType value for field with dbname (we use FCSXMLField Object and self.dbDataTypeForDBFieldName for this) ''' self.logger("dataTypeForDBFieldName() Retrieving dataType for dbname:'%s'" % (dbname),'debug') myField = self.initFieldWithDBName(dbname) return myField.dataType
[docs] def dbDataTypeForDBFieldName(self,dbname): ''' Returns a datatype for a dbname ''' self.logger("dbDataTypeForDBFieldName() Retrieving dbDataType for dbname:'%s'" % (dbname),'debug') myField = self.initFieldWithDBName(dbname) return myField.dbDataType
[docs] def dbFieldNameForFieldName(self,fieldName): '''Returns a dbFieldName from a field name (FCSVR_CLIENT field name from FCS Read XML field name''' self.logger('Retrieving dbFieldName for field: %s' %fieldName,'debug') theField = self.initFieldWithFieldName(fieldName) if theField: return theField.dbname else: raise FCSFieldNotFoundError(fieldName)
[docs] def initFieldWithDBName(self,dbname): '''Returns a FCSXMLField object from a dbname.Uses fcsvr_client to retrieve all the necessary values.''' self.logger("initFieldWithDBName() Constructing field with dbname:%s" % dbname,'debug') ## URL encode our fieldname encodedDBName = urllib.quote(dbname) ## Run our fcsvr_client command. fcsvrCMDTXT = "'%s' getmd /field/%s --xml" % (self.pathToFCSVRClient,encodedDBName) fcsvrCMD = subprocess.Popen(fcsvrCMDTXT,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("initFieldWithDBName() fcsvr_client command: fcsvr_client getmd /field/%s --xml" % dbname,'debug') if not fcsvrCMD.returncode == 0: return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDTXT) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger("Could not parse output from fcsvr_client command: fcsvr_client getmd /field/%s --xml" % dbname,'error') return False try: myField = FCSXMLField() for rootElement in myDom.childNodes[0].childNodes: ##self.logger("Nodename: %s" %rootElement.nodeName,'debug') if rootElement.nodeName == "values": for value in rootElement.getElementsByTagName("value"): valueID = value.attributes["id"].value #self.logger("valueID: %s" % valueID,'debug') if valueID == "FIELD_NAME": myField.name = value.childNodes[1].childNodes[0].data if valueID == "FIELD_DATA_TYPE": myField.setDBDataType(value.childNodes[1].childNodes[0].data) if valueID == "FIELD_ID": myField.dbname = value.childNodes[1].childNodes[0].data except: self.logger("Uncaught exception reading field name for dbname:%s" % dbname,'debug') return False if myField and myField.dbname == dbname: self.logger("Found field for dbname: %s, dataType:%s"% (dbname,myField.dataType),'debug') return myField elif not myField.dbname == dbname: raise RuntimeError("Constructed field does not match dbname:%s" % dbname) else: raise FCSFieldNotFoundError(dbname=dbname)
[docs] def initFieldWithFieldName(self,fieldName): '''Returns a FCSXMLField object from a field name. Uses fcsvr_client to retrieve all the necessary values.''' self.logger("Constructing field with name:%s" % fieldName,'debug') ## Run our fcsvr_client command. fcsvrCMDTXT = '"%s" search --crit "%s" /field --xml' % (self.pathToFCSVRClient,fieldName) fcsvrCMD = subprocess.Popen(fcsvrCMDTXT,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvr_client command: fcsvr_client search --crit "%s" /field --xml' % fieldName,'debug') if not fcsvrCMD.returncode == 0: return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDTXT) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client command: fcsvr_client search --crit "%s" /field --xml' % fieldName,'error') return False didComplete = False #try: testFieldName = "" searchRoot = myDom.childNodes[0] searchResultCount = 0 exactMatches = [] caseInsensitiveMatches = [] partialMatches = [] for searchResult in searchRoot.childNodes: ## self.logger("initFieldWithFieldName() Searching nodename: %s" % searchResult.nodeName,'debug') if searchResult.nodeName == "values": didFindMatch = False matchType = "" dbFieldName = "" for rootValue in searchResult.getElementsByTagName("value"): ## self.logger("initFieldWithFieldName() rootValue nodename: %s" % rootValue.nodeName,'debug') rootValueID = rootValue.attributes["id"].value ## self.logger("initFieldWithFieldName() Searching rootValueID: %s" % rootValueID,'debug') if rootValueID == "COMPLETE": didComplete = rootValue.childNodes[1].childNodes[0].data if not didComplete: break; searchResultCount += 1 elif rootValueID == "METADATA": myField = FCSXMLField() for value in rootValue.getElementsByTagName("values")[0].childNodes: if not value.nodeType == 1: ##self.logger("initFieldWithFieldName() Skipping nodetype: %s" % value.nodeType,'debug') continue try: valueID = value.attributes["id"].value ##self.logger("initFieldWithFieldName() - Found METADATA value node" ##" with ID: %s" % valueID,'debug') except: continue if valueID == "FIELD_NAME": myField.name = value.childNodes[1].childNodes[0].data self.logger("initFieldWithFieldName() Testing field with name: %s" % myField.name,'debug') if not myField.name: break; elif myField.name == fieldName: didFindMatch = True matchType = "exact" elif myField.name.lower() == fieldName.lower(): didFindMatch = True matchType = "caseinsensitive" else: if len(myField.name) > len(fieldName): if testFieldName[0:len(fieldName)].lower() == fieldName.lower(): didFindMatch = True matchType = "substring" else: if fieldName[0:len(myField.name)].lower() == myField.name.lower(): didFindMatch = True matchType = "substring" elif valueID == "FIELD_ID": myField.dbname = value.childNodes[1].childNodes[0].data elif valueID == "FIELD_DATA_TYPE": dbDataType = value.childNodes[1].childNodes[0].data ## if datatype is ktAtom, then we are a system field. if dbDataType == 'KtAtom' or dbDataType == 'KtAddress': continue try: myField.setDBDataType(dbDataType) except: self.logger('An unknown error occurred setting dataType:%s' ', skipping field.)' % dbDataType,'warning') continue if didFindMatch and myField: self.logger("initFieldWithFieldName() Found match:%s for fieldname: %s" % (matchType,myField.name),'debug') if matchType == "exact": exactMatches.append(myField) elif matchType == "caseinsensitive": caseInsensitiveMatches.append(myField) elif matchType == "substring": partialMatches.append(myField) #except: # self.logger("Uncaught exception reading field name for dbFieldName:%s" % dbFieldName,'debug') # return False myField = "" ## analyze our findings if len(exactMatches) == 1: self.logger("Found exact match for field name:%s, dbname:%s" % (fieldName,exactMatches[0].dbname),'warning') myField = exactMatches[0] elif len(exactMatches) > 1: self.logger("Found %s exact matches for field name:%s, determining best result" % (len(exactMatches),fieldName),'warning') result = "" ## Determine the most appropriate match based on DB Name, order of preference: ## PA_MD_CUST_, CUST_ currentResultPriority = 0 for field in exactMatches: dbname = field.dbname ## First look for an exact match for our field, using our field name ## with caps. idealPartialDBName = field.name.replace(" ","_").upper() ##self.logger('initFieldWithFieldName() idealPartialName:%s, %s' ##% (idealPartialDBName,dbname[0:5+len(idealPartialDBName)]),'debug') if dbname[0:11+len(idealPartialDBName)] == "PA_MD_CUST_%s" % idealPartialDBName: self.logger("Found match for ideal DB Name: %s for field name:%s" % (field.dbname,field.name),'debug') myResultPriority = 10 if myResultPriority > currentResultPriority: result = field currentResultPriority = myResultPriority elif dbname[0:5+len(idealPartialDBName)] == "CUST_%s" % idealPartialDBName: self.logger("Found match for ideal DB Name: %s for field name:%s" % (dbname,field.name),'debug') myResultPriority = 9 if myResultPriority > currentResultPriority: result = field currentResultPriority = myResultPriority elif dbname[0:11] == "PA_MD_CUST_": self.logger("Found PA_MD_CUST type field:%s for field name:%s" % (dbname,field.name),'debug') result = field myResultPriority = 8 if myResultPriority > currentResultPriority: result = field currentResultPriority = myResultPriority elif dbname[0:5] == "CUST_": self.logger("Found CUST_ type field:%s for field name:%s" % (dbname,field.name),'debug') result = field myResultPriority = 7 if myResultPriority > currentResultPriority: result = field currentResultPriority = myResultPriority if result: myField = result else: self.logger("Returning last result:%s for field name:'%s'" % (dbname,fieldName),'warning') myField = field elif len(caseInsensitiveMatches) == 1: myField = caseInsensitiveMatches[0] elif len(caseInsensitiveMatches) > 1: self.logger("Found %s matches for field name:%s, returning first result!" % (len(exactMatches),fieldName),'warning') myField = caseInsensitiveMatches[0] elif len(partialMatches) == 1: self.logger("Found more than one partial match for field name:%s, returning first result" % fieldName,'warning') myField = partialMatches[0] elif len(partialMatches) > 1: self.logger("Found %s matches for field name:%s, returning first result!" % (len(exactMatches),fieldName),'warning') myField = partialMatches[0] else: raise RuntimeError("An error occured while determining preferred value from matches for fieldName:%s!" % fieldName) if myField: self.logger("Found dbFieldName: %s for fieldName: %s" % (myField.dbname,myField.name),'debug') return myField else: return False
[docs] def assetWithTitle(self,title,mdSet=''): '''Returns a new FCSVRClient object based upon search results''' title = title.replace('`',"'").replace('"',"'") self.logger('Retrieving Asset for name:%s' % title,'debug') ## Run our fcsvr_client command. ''' fcsvrCMDTXT = ('"%s" search --crit "%s" /asset --xml' % (self.pathToFCSVRClient,title)) ''' #### Generate our search XML file ## Create our title field myTitleField = FCSXMLField(name='Title',dbname='CUST_TITLE',value=title) XMLSearchFilePath = self.generateSearchXML(fields=[myTitleField]) fcsvrCMDTXT = ('"%s" search /asset --xml --xmlcrit < "%s"' % (self.pathToFCSVRClient,XMLSearchFilePath)) fcsvrCMD = subprocess.Popen(fcsvrCMDTXT, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvr_client command:\'%s\'' % fcsvrCMDTXT,'debug') if not self.keepFiles: os.remove(XMLSearchFilePath) if not fcsvrCMD.returncode == 0: return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDTXT) ## Delete our temp search file if not self.keepFiles: os.remove(XMLSearchFilePath) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client command: \'%s\'' % fcsvrCMDTXT,'error') raise RuntimeError('fcsvr_client returned unexpected results!') didComplete = False #try: matches = [] searchRoot = myDom.childNodes[0] searchResultCount = 0 ## Iterate through the DOM and extract our results. for searchResult in searchRoot.childNodes: ##self.logger("productionWithTitle() Searching nodename: %s" % searchResult.nodeName,'debug') if searchResult.nodeName == 'values': searchResultDict = {} didFindMatch = False for rootValue in searchResult.getElementsByTagName('value'): ## This is the result level, here for each result ##self.logger("productionWithTitle() rootValue nodename: %s" % rootValue.nodeName,'debug') rootValueID = rootValue.attributes['id'].value ##self.logger('productionWithTitle() Searching rootValueID: %s' % rootValueID,'debug') if rootValueID == 'COMPLETE': didComplete = rootValue.childNodes[1].childNodes[0].data if not didComplete: break; searchResultCount += 1 elif rootValueID == 'METADATA': for value in rootValue.getElementsByTagName('value'): valueID = value.attributes['id'].value ##self.logger('assetWithTitle() Searching value: %s' % valueID,'debug') if valueID == 'CUST_TITLE': testTitle = value.childNodes[1].childNodes[0].data if not testTitle: break; elif testTitle == title: didFindMatch = True searchResultDict['matchType'] = 'exact' elif testTitle.strip() == title.strip(): didFindMatch = True searchResultDict['matchType'] = 'exact_whitespace' elif testTitle.lower() == title.lower(): didFindMatch = True searchResultDict['matchType'] = 'caseinsensitive' elif testTitle.lower().strip() == title.lower().strip(): didFindMatch = True searchResultDict['matchType'] = 'caseinsensitive_whitespace' elif testTitle in title: didFindMatch = True searchResultDict['matchType'] = 'substring' else: if len(testTitle) > len(title): if testTitle[0:len(title)].lower() == title.lower(): didFindMatch = True searchResultDict['matchType'] = 'substring' elif testTitle.lower() in title.lower(): didFindMatch = True searchResultDict['matchType'] = 'substring' else: if title[0:len(testTitle)].lower() == testTitle.lower(): didFindMatch = True searchResultDict['matchType'] = 'substring' elif testTitle in title: didFindMatch = True searchResultDict['matchType'] = 'substring' elif valueID == 'ASSET_TYPE': searchResultDict['ASSET_TYPE'] = value.childNodes[1].childNodes[0].data elif valueID == 'ASSET_NUMBER': searchResultDict['ASSET_NUMBER'] = value.childNodes[1].childNodes[0].data if didFindMatch and 'ASSET_NUMBER' in searchResultDict: matches.append(searchResultDict) #except: # self.logger("Uncaught exception reading field name for dbFieldName:%s" % dbFieldName,'debug') # return False #### Analyze our findings ## If only one match was found, return it theMatch = '' if len(matches) == 1: self.logger('Found Asset: /asset/%s for search string: \'%s\'' % (matches[0]['ASSET_NUMBER'],title),'detailed') self.logger(' Match Type: %s' % matches[0]['matchType'],'debug') theMatch = matches[0] elif not matches: self.logger('Found no assets matching search string: \'%s\'' % title,'error') raise FCSEntityNotFoundError(entityType='asset',entityTitle=title) elif matches > 1: self.logger('Found %s assets matching search string: \'%s\'' % (len(matches),title),'detailed') rankedMatches = [] topScore = 0 topScoreConflict = False ## Iterate through our matches for ranking. for result in matches: searchScore = 0 if result['matchType'] == 'exact': searchScore += 50 elif result['matchType'] == 'exact_whitespace': searchScore += 45 elif result['matchType'] == 'caseinsensitive': searchScore += 25 elif result['matchType'] == 'caseinsensitive_whitespace': searchScore += 20 elif result['matchType'] == 'substring_whitespace': searchScore += 2 elif result['matchType'] == 'substring': searchScore += 1 if 'ASSET_TYPE' in result and mdSet and result['ASSET_TYPE'].lower() == mdSet.lower(): searchScore += 50 result['searchScore'] = searchScore ## Insert it in the appropriate rank, based on searchScore, break when we ## get to an object who's score is less then ours count=0 while count < len(rankedMatches): if rankedMatches[count]['searchScore'] < result['searchScore']: break elif (rankedMatches[count]['searchScore'] == result['searchScore'] and result['searchScore'] == topScore): self.logger('assetWithTitle() Found top score conflict: %s asset:' ' /project/%s' % (topScore,result['ASSET_NUMBER']),'debug') topScoreConflict = True count += 1 else: count += 1 ## insert our object if count == 0: if result['searchScore'] > topScore: self.logger('assetWithTitle() Found new top score: %s asset: /asset/%s' % (result['searchScore'],result['ASSET_NUMBER']),'debug') topScore = result['searchScore'] topScoreConflict = False rankedMatches.insert(count,result) if topScoreConflict: self.logger('Found more than one Asset with same search score: %s,' ' returning first result! (/asset/%s) ' % (topScore,rankedMatches[0]['ASSET_NUMBER']),'warning') theMatch = rankedMatches[0] else: self.logger('Found more than one Asset, returning result with ' 'highest search score:%s! (/asset/%s) ' % (rankedMatches[0]['searchScore'], rankedMatches[0]['ASSET_NUMBER']) ,'detailed') theMatch = rankedMatches[0] if theMatch: theAsset = FCSVRClient() theAsset.entityType = 'asset' theAsset.entityID = theMatch['ASSET_NUMBER'] theAsset.entityMetadataSet = theMatch['ASSET_TYPE'] else: raise RuntimeError('No asset found for title: \'%s\'' % title) return theAsset
[docs] def productionWithTitle(self,title,mdSet="",match='exact'): '''Returns a new FCSVRClient object based upon search results. Matching parameters can be defined as either "exact" or "substring"''' title = title.replace("`","'").replace('"',"'") if not title or title == " ": msg = 'Could not load production, an empty title was provided!' self.logger(msg,'error') raise FCSProductionLoadError(msg) self.logger("Retrieving Production for name:%s" % title,'debug') ## Run our fcsvr_client command. ##fcsvrCMDTXT = '"%s" search --crit "%s" /project --xml' % (self.pathToFCSVRClient,title) #### Generate our search XML file ## Create our title field myTitleField = FCSXMLField(name='Title',dbname='CUST_TITLE',value=title) XMLSearchFilePath = self.generateSearchXML(fields=[myTitleField],match=match) fcsvrCMDTXT = ('"%s" search /project --xml --xmlcrit < "%s"' % (self.pathToFCSVRClient,XMLSearchFilePath)) fcsvrCMD = subprocess.Popen(fcsvrCMDTXT,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvr_client command: fcsvr_client search /project --xml' ' --xmlcrit < "%s"' % XMLSearchFilePath,'debug') if not fcsvrCMD.returncode == 0: return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDTXT) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client command: ' 'fcsvr_client search /project --xml --xmlcrit < "%s"' % XMLSearchFilePath, 'error') raise RuntimeError("fcsvr_client returned unexpected results!") didComplete = False #try: matches = [] searchRoot = myDom.childNodes[0] searchResultCount = 0 ## Iterate through the DOM and extract our results. for searchResult in searchRoot.childNodes: ##self.logger("productionWithTitle() Searching nodename: %s" % searchResult.nodeName,'debug') if searchResult.nodeName == "values": searchResultDict = {} didFindMatch = False for rootValue in searchResult.getElementsByTagName("value"): ## This is the result level, here for each result ##self.logger("productionWithTitle() rootValue nodename: %s" % rootValue.nodeName,'debug') rootValueID = rootValue.attributes["id"].value ##self.logger("productionWithTitle() Searching rootValueID: %s" % rootValueID,'debug') if rootValueID == "COMPLETE": didComplete = rootValue.childNodes[1].childNodes[0].data if not didComplete: break; searchResultCount += 1 elif rootValueID == "METADATA": for value in rootValue.getElementsByTagName("value"): valueID = value.attributes["id"].value self.logger("productionWithTitle() Searching value: %s" % valueID,'debug') if valueID == "CUST_TITLE": testTitle = value.childNodes[1].childNodes[0].data if not testTitle: break; elif testTitle == title: didFindMatch = True searchResultDict["matchType"] = "exact" elif testTitle.lower() == title.lower(): didFindMatch = True searchResultDict["matchType"] = "caseinsensitive" else: if len(testTitle) > len(title): if testTitle[0:len(title)].lower() == title.lower(): didFindMatch = True searchResultDict["matchType"] = "substring" else: if title[0:len(testTitle)].lower() == testTitle.lower(): didFindMatch = True searchResultDict["matchType"] = "substring" elif valueID == "PROJECT_TYPE": searchResultDict["PROJECT_TYPE"] = value.childNodes[1].childNodes[0].data elif valueID == "PROJECT_NUMBER": searchResultDict["PROJECT_NUMBER"] = value.childNodes[1].childNodes[0].data if didFindMatch and "PROJECT_NUMBER" in searchResultDict: matches.append(searchResultDict) #except: # self.logger("Uncaught exception reading field name for dbFieldName:%s" % dbFieldName,'debug') # return False #### Analyze our findings ## If only one match was found, return it theMatch = "" if len(matches) == 1: self.logger("Found Production: /project/%s for search string: '%s'" % (matches[0]["PROJECT_NUMBER"],title),"detailed") theMatch = matches[0] elif not matches: self.logger("Found no productions matching search string: '%s'" % title,'error') raise RuntimeError("No production found for title: '%s'" % title) elif matches > 1: self.logger("Found %s productions matching search string: '%s'" % (len(matches),title),"detailed") rankedMatches = [] topScore = 0 topScoreConflict = False ## Iterate through our matches for ranking. for result in matches: searchScore = 0 if result["matchType"] == "exact": searchScore += 50 elif result["matchType"] == "caseinsensitive": searchScore += 25 elif result["matchType"] == "substring": searchScore += 1 if "PROJECT_TYPE" in result and mdSet and result["PROJECT_TYPE"].lower() == mdSet.lower(): searchScore += 50 result["searchScore"] = searchScore ## Insert it in the appropriate rank, based on searchScore, break when we ## get to an object who's score is less then ours count=0 while count < len(rankedMatches): if rankedMatches[count]["searchScore"] < result["searchScore"]: break elif rankedMatches[count]["searchScore"] == result["searchScore"] and result["searchScore"] == topScore: self.logger("productionWithTitle() Found top score conflict: %s production: /project/%s" % (topScore,result["PROJECT_NUMBER"]),'debug') topScoreConflict = True count += 1 else: count += 1 ## insert our object if count == 0: if result["searchScore"] > topScore: self.logger("productionWithTitle() Found new top score: %s production: /project/%s" % (result["searchScore"],result["PROJECT_NUMBER"]),'debug') topScore = result["searchScore"] topScoreConflict = False rankedMatches.insert(count,result) if topScoreConflict: self.logger("Found more than one Production with same search score: %s, returning first result! (/project/%s) " % (topScore,rankedMatches[0]["PROJECT_NUMBER"]),'warning') theMatch = rankedMatches[0] else: self.logger("Found more than one Production, returning result with higest search score:%s! (/project/%s) " % (rankedMatches[0]["searchScore"],rankedMatches[0]["PROJECT_NUMBER"]),"detailed") theMatch = rankedMatches[0] if theMatch: theProduction = FCSVRClient() theProduction.entityType = "project" theProduction.entityID = theMatch["PROJECT_NUMBER"] theProduction.entityMetadataSet = theMatch["PROJECT_TYPE"] else: raise RuntimeError("No production found for title: '%s'" % title) return theProduction
[docs] def createProduction(self,title="",mdSet="",parentProjectAddress="",setMD=False): '''Creates a production. ''' ## Method to create a production, If our entity type is not 'project', ## and we are not passed a title abort. If our entity type is 'project', ## try to read our title from our fields. if not mdSet: mdSet = self.defaultProductionMetadataSet if not mdSet: self.logger("Could not create production: no metadata set specified!",'error') raise FCSProductionLoadError("Could not create production! No metadata set specified!") if not title: title = self.valueForField("Title") if not title: self.logger("Could not create production: no title specified!",'error') raise FCSProductionLoadError("Could not create production! No title specified!") ## if we are currently a project type asset, load ourselves, otherwise, ## return a duplicated object. if self.entityType == "project": obj = self else: obj = copy.copy(self) ## If setMD is set to true and we have no fields at this point, throw a ## warning and change setMD to false if setMD and not self.fields: self.logger("Creating production with metadata, but no fields are loaded! (No metadata to set)",'warning') setMD = False ## Set our title field titleField = FCSXMLField(name="Title",dbname="CUST_TITLE",value=title) obj.setField(titleField) ## Construct our fcsvr_client arguments cmdArgs = ' create /project --type %s' % mdSet if parentProjectAddress: cmdArgs += ' --linkparentaddr %s --linkparentlinktype 16' % parentProjectAddress ## If we are importing MD, insert our file args ## note 2/7/09: this is temporarily disable until I fix the XML output ## to ensure proper formatting during asset creation, for now we're ## just calling setMD after the asset is created, which works fine. ''' tempFilePath = "" if setMD: tempFilePath = self.generateTempXMLFile() if os.path.isfile(tempFilePath): cmdArgs += ' --xml "%s"' % tempFilePath else: self.logger("XML File does not exist at path:'%s', cannot import metadata!",'error') cmdArgs += ' CUST_TITLE="%s"' % (title.replace('"',"'")) else: cmdArgs += ' CUST_TITLE="%s"' % (title.replace('"',"'")) ''' cmdArgs += ' CUST_TITLE="%s"' % (title.replace('"',"'")) if not os.geteuid() == 0: if not self.useSudo: self.logger("Could not set Metadata, root privileges are required!",'error') raise FCSProductionLoadError("Updating metadata requires root privileges!") useSudo = True else: useSudo = False if useSudo: fcsvrCMDTXT = "/usr/bin/sudo '%s' %s" % (self.pathToFCSVRClient,cmdArgs) else: fcsvrCMDTXT = "'%s' %s" % (self.pathToFCSVRClient,cmdArgs) self.logger('fcsvrCMD:\n %s' % fcsvrCMDTXT,'debug') ## run fcsvr_client fcsvrCMD = subprocess.Popen('%s' % fcsvrCMDTXT ,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("fcsvr_client output: %s" % fcsvrCMD_STDOUT,'debug') if not fcsvrCMD.returncode == 0: errorString = '%s\n%s' % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR) self.logger("FCSVRClient Error: %s" % errorString,'error') raise FCSProductionLoadError('Could not create production!',errorString,fcsvrCMDTXT) ## Resolve our entity ID from STDOUT try: match = re.match("/project/(\d+)",fcsvrCMD_STDOUT) projectID = match.group(1) except: self.logger("Could not retrieve project id from string: %s" % fcsvrCMD_STDOUT,'error') raise FCSProductionLoadError('Could not find /project/id in results!',fcsvrCMD_STDOUT,'fcsvrCMDTXT') self.logger("Created new Production with ID:%s" % projectID,'debug') obj.entityID = projectID obj.entityType = "project" obj.entityMetadataSet = mdSet ## If we have a temp file, delete it ## Disabled for now in lieu of setMD ''' if not self.debug: os.remove(tempFilePath) ''' if setMD: obj.setMD() return obj
[docs] def removeMemberFromProduction(self,member='',memberPath='',production='', productionPath='',tempProductionMDSet=''): ''' Removes a member from a production. Accepts either an FCSVRClient object (member) or a FCS entity path '/asset/10' (memberPath) in a addition to a FCSVRClient object with project entityType (production) or a production path '/project/10' (productionPath). If calling instance is either a production or an asset, you can omit the appropriate arguments. We also accept tempProductionMDSet: which is the md set used to create a temporary production. (We do this because FCS can't explicitely delete an existing link, but it CAN relocate links).''' ## Resolve our child (asset or production) and parent (production) childObject = '' parentObject = '' ## Resolve our child (asset or production) if member: childObject = member elif memberPath: self.logger("Resolving member from entityPath:%s" % memberPath,'debug') pathMatch = re.match('\/(.*)\/(.*)',memberPath) if pathMatch: memberType = pathMatch.groups()[0] memberID = pathMatch.groups()[1] childObject = FCSVRClient(entityType=memberType,id=memberID) else: if (self.entityType == 'asset' or self.entityType == 'project') and self.entityID: childObject = self ## Resolve our parent (production) if production: parentObject = production elif productionPath: self.logger("Resolving parent production from entityPath:%s" % productionPath,'debug') pathMatch = re.match('\/(.*)\/(.*)',memberPath) if pathMatch: memberType = pathMatch.groups()[0] memberID = pathMatch.groups()[1] parentObject = FCSVRClient(entityType=memberType,id=memberID) else: raise RuntimeError('Could not remove member from production, parentPath:%s is invalid!') else: self.logger("Resolving parent production from local instance!",'debug') if self.entityType == 'project' and self.entityID: ##print "self:%s" % self parentObject = self ##print "parent:%s" % parentObject ##print "parentObject.entityPath:%s" % parentObject.debug else: raise RuntimeError('Could not remove member from production, parentPath:%s is invalid!') ## At this point we should have both a child and parent object, perform ## sanity checks. ## Verify we have a parent object and it is a production if not parentObject: self.logger('removeMemberFromProduction() cannot continue: could not resolve parent production!','error') raise RuntimeError('Cannot continue: could not resolve parent production!') elif parentObject.entityType != 'project': self.logger('removeMemberFromProduction() cannot continue: provided parent is not a production!','error') raise RuntimeError('Cannot continue: provided parent is not a production!') ## Verify we have a child object. if not childObject: self.logger('removeMemberFromProduction() cannot continue: could not resolve member!','error') raise RuntimeError('removeMemberFromProduction() cannot continue: could not resolve member!','error') ## Verify the child and parent aren't referencing the same entity. parentEntityPath = parentObject.entityPath() childEntityPath = childObject.entityPath() if parentEntityPath == childEntityPath: self.logger('removeMemberFromProduction() cannot continue: provided ' ' member and production are the same:%s!' % parentObject.entityPath,'error') raise RuntimeError('removeMemberFromProduction() cannot continue: provided ' ' member and production reference the same entity: %s!' % parentObject.entityPath,'error') ## At this point we have a legitimate child and parent, proceed to remove ## child from parent. ## Get current production members try: currentMembers = parentObject.productionMemberAddresses() except: currentMembers = [] if not childEntityPath in currentMembers: self.logger('Could not remove entity:%s from production:%s, %s is not a ' 'current member!' % (childEntityPath, parentEntityPath, childEntityPath),'error') return False ## Get a list of all our child's current parents childCurrentParents = childObject.parentProductions() ## Create a new temp production and use --movelink, which will destroy ## all of the assets production memberships,we then rebuild all of ## the memberships, short of the production which we are being removed ## from. Necessarily convoluted until fcsvr_client can explicitely remove ## links, but it works. tempProd = FCSVRClient(entityType='project') self.logger('removeMemberFromProduction() Creating temporary production' ' with mdset:%s' % tempProductionMDSet) tempProd.createProduction(title='_temp_',mdSet=tempProductionMDSet) tempProd.fcsvr_client_make_link(linkType=1, parentPath=tempProd.entityPath(), childPath=childEntityPath, moveLink=True) ## Delete the temp production, the asset is now no longer a member of ## ANY productions. tempProd.delete() ## Iterate through each pre-exsisting production link, and ## re-establish membership (provided it is not the provided parent ## production, which we will NOT rejoin). (We do this ## this way because fcsvr_client does not have support to explicitely ## remove links, all we have is --movelink) for parentPath in childCurrentParents: ## if we match our current production, which we are being removed ## from, skip it. if parentEntityPath == parentPath: print "Removing asset:%s from production:%s" % (childEntityPath, parentPath) continue ## re-establish membership to the current parentPath self.fcsvr_client_make_link(linkType=1, parentPath=parentPath, childPath=childEntityPath, moveLink=False) ## Flush our caches self.flushCaches() return True
[docs] def addMemberToProduction(self,member="",memberPath="",production="",productionPath="",moveLink=False): ''' Adds entity to the specified production. Accepts either a FCSVRClient object (member) or a FCS entity path '/asset/10' (memberPath) in addition to a FCSVRClient object with entityType 'production', or a production path '/project/10'. If the local entity is either the member to be added, or is a production, then you can omit that information. For instance, if we are a loaded production, I can call: self.addMemberToProduction(memberPath='/asset/10'), then we will add asset with ID 10 to our own membership. Likewise, if we call: self.addMemberToProduction(productionPath='/project/12', then we will add ourself (a production) as a child production to '/project/12' ''' ## If no member information is given, assume that we are the member if not member and not memberPath: theMemberPath = self.entityPath() elif member and not memberPath: theMemberPath = member.entityPath() elif memberPath: theMemberPath = memberPath if not production and not productionPath and self.entityType == 'project': theProductionPath = self.entityPath() elif production and not productionPath and production.entityType == 'project': theProductionPath = production.entityPath() elif productionPath: theProductionPath = productionPath ## Make sure that our memberPath references either an asset or project ## Address try: memberType = theMemberPath.split('/')[1] if not memberType == "project" and not memberType == "asset": raise except: self.logger('Member path: %s is not a project or asset, cannot continue!' % theMemberPath,'error') return False ## Make sure that our productionPath references a project address try: productionType = theProductionPath.split('/')[1] if not productionType == "project": raise except: self.logger('Production path: %s is not a project or asset, cannot continue!' % theProductionPath,'error') return False ## Make sure we never add ourself to ourself if theProductionPath == theMemberPath: self.logger('Cannot add Member Path: %s to Production path: %s, one cannot add one to oneself!' % (theMemberPath,theProductionPath),'error') return False ''' movelink is now a passable argument. ## Create our link if memberType == 'project': moveLink = True else: moveLink = False ''' return self.fcsvr_client_make_link(1,parentPath=theProductionPath,childPath=theMemberPath,moveLink=moveLink)
[docs] def addAssetToProduction(self,asset="",assetID="",assetPath="",productionPath="",productionID=""): ''' Adds an asset to production, 07/22/10: this is depricated, use addMemberToProduciton''' ## Resolve our asset information if not asset and not assetID and not assetPath: self.logger("addAssetToProduction() No asset information provided, aborting",'error') return False if asset: if assetID and not asset.entityID == assetID: self.logger("addAssetToProduction() provided conflicting asset:%s and assetID:%s" % (asset.entityID,assetID),'error') return False if assetPath and not asset.entityPath() == assetPath: self.logger("addAssetToProduction() provided conflicting asset:%s and assetPath:%s" % (asset.entityPath(),assetPath),'error') return False assetPath = asset.entityPath() assetID = asset.entityID elif assetID: if assetPath and not "/asset/%s" % assetID == assetPath: self.logger("addAssetToProduction() provided conflicting assetID:%s and assetPath:%s" % (assetID,assetPath),'error') return False else: assetPath = "/asset/%s" % assetID ## Resolve our production information if not productionPath and not productionID and not self.entityType == "project": self.logger("addAssetToProduction() No production information provided, aborting",'error') return False elif not productionPath and not productionID and self.entityType == "project": productionID = self.entityID productionPath = self.entityPath() elif productionID: if productionPath and not "/project/%s" % productionID == productionPath: self.logger("addAssetToProduction() provided conflicting assetID:%s and assetPath:%s" % (assetID,assetPath),'error') return False assetPath = "/asset/%s" % assetID ## At this point we should have an assetPath and a productionPath if not assetPath and not productionPath: self.logger("addAssetToProduction() Could not resolve assetPath or productionPath,cannot continue!",'error') return False ## Create our link self.logger("Adding asset:%s to production:%s" % (assetPath,productionPath),'error') return self.fcsvr_client_make_link(1,parentPath=productionPath,childPath=assetPath)
[docs] def assetsFromProduction(self,productionID="",productionTitle="",recurse=False,mdSet=""): '''Function which returns a list of associated FCSVRClient asset objects. If recurse is set to true, we will return assets for subproductions as well. If mdSet is provided, we will return only assets which have the provided metadataset. ''' if not productionID and not productionTitle: if self.entityType == "project" and self.entityID: productionID = self.entityID elif not self.entityType == "project": self.logger("Loaded entity is not a project and no ID or Title specified!",'error') raise RuntimeError("Loaded entity is not a project and no ID or Title specified!",'error') else: self.logger("Must provide production ID or Title to search!",'error') raise RuntimeError("Loaded entity is not a project and no ID or Title specified!",'error') elif not productionID and productionTitle: try: production = self.productionWithTitle(title=productionTitle) productionID = production.entityID except: raise RuntimeError("Could not load production with Title: %s" % productionTitle) if not productionID: self.logger("") raise RuntimeError("No production could be found!") ## Retrieve all assets in the production. assetAddresses = self.productionAssetAddresses(productionID=productionID,recurse=recurse) assets = [] ## Iterate through each returned asset address and instantiate. for assetAddress in assetAddresses: regexMatch = re.match('/(.*)/(.*)',assetAddress) memberType = regexMatch.group(1) memberID = regexMatch.group(2) myAsset = FCSVRClient(id=memberID) myAsset.initWithAssetID(memberID) ## If no mdSet parameter was passed, otherwise ensure that our ## mdSet matches. if not mdSet: assets.append(myAsset) elif mdSet and myAsset.entityMetadataSet == mdSet: assets.append(myAsset) elif mdSet and not myAsset.entityMetadataSet == mdSet: self.logger('assetsFromProduction() Asset: %s with mdSet: %s does not match' ' mdSet: %s, excluding!' % (myAsset.entityPath(),myAsset.entityMetadataSet,mdSet),'debug') return assets
[docs] def productionAssetAddresses(self,productionID="",productionTitle="",recurse=False,processedAddresses=""): '''Function which returns asset addresses '/asset/12' from a production. If recurse is set to true, we will recurse any nested productions and collate their addresses. The processedAddresses parameter is an array of previously processed production addresses to prevent infinite recursion scenarios''' if not productionID and not productionTitle: if self.entityType == "project" and self.entityID: productionID = self.entityID elif not self.entityType == "project": self.logger("Loaded entity is not a project and no ID or Title specified!",'error') raise RuntimeError("Loaded entity is not a project and no ID or Title specified!",'error') else: self.logger("Must provide production ID or Title to search!",'error') raise RuntimeError("Loaded entity is not a project and no ID or Title specified!",'error') elif not productionID and productionTitle: try: production = self.productionWithTitle(title=productionTitle) productionID = production.entityID except: raise RuntimeError("Could not load production with Title: %s" % productionTitle) if not productionID: self.logger("") raise RuntimeError("No production could be found!") memberAddresses = self.productionMemberAddresses(productionID=productionID, productionTitle=productionTitle) assetAddresses = [] productionAddresses = [] processedProductionAddresses = ["/project/%s" % productionID] for memberAddress in memberAddresses: regexMatch = re.match('/(.*)/(.*)',memberAddress) memberType = regexMatch.group(1) memberID = regexMatch.group(2) if memberType == "asset": if memberAddress not in assetAddresses: assetAddresses.append(memberAddress) elif (memberType == "project" and recurse and memberAddress not in processedProductionAddresses): memberAddresses.extend(self.productionMemberAddresses(productionID=memberID)) return assetAddresses
[docs] def productionMemberAddresses(self,productionID="",productionTitle=""): '''Function which returns a list of associated entity addresses: ["/asset/12", "/project/11"] ''' if not productionID and not productionTitle: if self.entityType == "project" and self.entityID: productionID = self.entityID elif not self.entityType == "project": self.logger("Loaded entity is not a project and no ID or Title specified!",'error') raise RuntimeError("Loaded entity is not a project and no ID or Title specified!",'error') else: self.logger("Must provide production ID or Title to search!",'error') raise RuntimeError("Loaded entity is not a project and no ID or Title specified!",'error') elif not productionID and productionTitle: try: production = self.productionWithTitle(title=productionTitle) productionID = production.entityID except: raise RuntimeError("Could not load production with Title: %s" % productionTitle) if not productionID: self.logger("") raise RuntimeError("No production could be found!") ## Get our asset members. assetAddresses = self.getValueForLinkType(value="ADDRESS",linkType=1,id=productionID,type="project") ## Get our production members. try: projectAddresses = self.getValueForLinkType(value="ADDRESS",linkType=16,id=productionID,type="project") except: projectAddresses = [] memberAddresses = [] try: memberAddresses.extend(assetAddresses) memberAddresses.extend(projectAddresses) except: pass if len(memberAddresses) == 0: self.logger("No member addresses were found for production with id:'%s'" % productionID,'detailed') return memberAddresses
[docs] def assetsFromProject(self,projectID="",mdSet=''): '''Function which returns a list of associated FCSVRClient asset objects. For the provided FCP project.''' assetAddresses = self.projectElementAddresses(id=projectID,addressType='asset') assets = [] for assetAddress in assetAddresses: regexMatch = re.match('/(.*)/(.*)',assetAddress) memberType = regexMatch.group(1) memberID = regexMatch.group(2) myAsset = FCSVRClient(id=memberID,cfgParser=self.cfgParser) ## If no mdSet parameter was passed add our asset to the list, ## otherwise ensure that our mdSet matches. if not mdSet: assets.append(myAsset) elif mdSet and myAsset.entityMetadataSet == mdSet: assets.append(myAsset) elif mdSet and not myAsset.entityMetadataSet == mdSet: self.logger('assetsFromProject() Asset: %s with mdSet: %s does not match' ' mdSet: %s, excluding!' % (myAsset.entityPath(),myAsset.entityMetadataSet,mdSet),'debug') return assets
[docs] def projectElementAddresses(self,id='',addressType='asset'): '''Function which returns a list of element addresses associated to an FCP project file. If we are a loaded asset with '.fcp' in the file name, we will work of our loaded instance, otherwise we will create a new object. An optional argument, addressType, can be provided. When set to 'asset', we return /asset/ addresses for any linked elements. if set to 'element', we return /element addresses for all linked alements. ''' ## If we aren't provided an ID, check to make sure we are a loaded asset if not id and self.entityID and self.entityType == 'asset': myObject = self elif id and self.entityID == id: myObject = self elif id and not self.entityID == id: myObject = FCSVRClient(entityType='asset',id=id) else: msg = ('Could not load element addresses: no asset id provided and ' ' local instance is not a project file!') self.logger('projectElementAddresses() %s' % msg,'error') raise FCSObjectLoadError(msg) ## Make sure our resolved object is an FCP project asset (based on filename) fileName = myObject.valueForField('File Name') if not fileName[-4:] == '.fcp' and not fileName[-4:] == u'.fcp': msg = ('Could not load element addresses: no asset id provided and ' ' local instance is not a project file!') self.logger('projectElementAddresses() %s' % msg,'error') raise FCSObjectLoadError(msg) ## At this point we have our FCP asset loaded at myObject, fetch all ## child links with type 1, but first, flush our cache. self.flushCaches() elementList = self.getValueForLinkType(value="ADDRESS", linkType=1, id=myObject.entityID, type=myObject.entityType) ## If we are not filtering our results, return our current links if addressType == 'element': return elementList ## Iterate through our element list, create separate memberList based ## upon which elements have linked media assets. memberList = [] self.logger('projectElementAddresses() found elements: %s, processing!' % ','.join(elementList),'debug') for elementAddress in elementList: self.logger('projectElementAddresses() processing element: %s' % elementAddress,'debug') try: match = re.match('/(.*)/(.*)',elementAddress) elementEntityID = match.groups()[1] elementEntityType = match.groups()[0] memberAddress = self.getValueForLinkType(value='ADDRESS', linkType=12, id=elementEntityID, type=elementEntityType, origin='child') if memberAddress: memberList.append(memberAddress[0]) else: raise except: self.logger('projectElementAddresses() skiping element address:%s,' ' could not load asset address!' % elementAddress,'debug') continue return memberList
[docs] def parentProductions(self,id='',entityType='asset'): '''Function which returns a list of associated project addresses: ["/project/11"] representative of all productions that an asset is a member of, based on passed integer value assetID. assetID can be omitted if we are a loaded asset. ''' if not id and self.entityID and (self.entityType == 'asset' or self.entityType == 'project'): id = self.entityID entityType = self.entityType else: msg = "Loaded entity is not an asset or project and no ID was provided!" self.logger(msg,'error') raise RuntimeError(msg,'error') ## Get our parent production ## Fetch our XML DOM for our child links. childLinkDOM = self.getChildLinksXML(id=id,type=entityType) if entityType == 'project': linkType = 16 else: linkType = 1 parentAddresses = self.getValueForLinkType(value="ADDRESS", linkType=linkType, xmlDOM=childLinkDOM, id=id, type=entityType) ## Make sure we have parents if len(parentAddresses) == 0: raise RuntimeError("No parent productions found for entity:'/%s/%s'" % (entityType,id)) return parentAddresses
[docs] def archive(self,deviceID='',deviceDict='',recurseProductions=False): '''Archives the loaded entity. If this is a production, then we will archive all assets which are members of the production. If recurseProductions is true, then we will archive it's members as well. ''' ## Make sure that we have a specified archive device, if none are ## specified, archive to the first archive device found. if not deviceID and not deviceDict: self.logger('No archive device was provided, searching for applicable device','detailed') devicesMap = self.getDevicesMap() for deviceName,deviceDict in devicesMap.iteritems(): if 'DEV_ARCHIVE' in deviceDict and deviceDict['DEV_ARCHIVE']: deviceID = deviceDict['DEVICE_ID'] self.logger(' - Using archive device: %s' % deviceDict['DEVICE_NAME'],'detailed') break elif deviceID: deviceID = deviceID elif deviceDict: deviceID = deviceDict['DEVICE_ID'] ## If this is a production, restore all members of the production. if self.entityType == 'project': if recurseProductions: members = self.productionMemberAddresses() else: members = self.productionAssetAddresses() ## Iterate through our members and restore them for member in members: memberObj = FCSVRClient(entityPath=member) memberObj.loadConfiguration(self.configParser) memberObj.archive(recurseProductions=recurseProductions) ## If this is an asset, simply archive it. elif self.entityType == 'asset': ## Archive the asset ## Basic sanity checks. if not self.entityID: msg = 'Could not archive entity, no entityID is loaded.' self.logger(msg,'error') raise FCSError(msg) ## Run our fcsvr_client command. if not os.geteuid() == 0: if not self.useSudo: self.logger('Could not archive entity: root privileges are required!','error') raise FCSProductionLoadError("Archiving entities requires root privileges!") useSudo = True else: useSudo = False if useSudo: fcsvrCMDString = ('/usr/bin/sudo "%s" archive %s /dev/%s' % (self.pathToFCSVRClient,self.entityPath(),deviceID)) else: fcsvrCMDString = ('"%s" archive %s /dev/%s' % (self.pathToFCSVRClient,self.entityPath(),deviceID)) self.logger('fcsvrCMD:\n %s' % fcsvrCMDString,'debug') fcsvrCMD = subprocess.Popen(fcsvrCMDString,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("fcsvr_client output: %s %s" % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR),'debug') if not fcsvrCMD.returncode == 0: ## If fcsvr_client reports an error, search the text for the string ## 'The asset is already offline' (we must do this because ## fcsvr_client archive does return xml) if 'The asset is already offline' in fcsvrCMD_STDOUT: raise FCSAssetOfflineError(assetID=self.entityID) elif 'Archiving FCP Project assets is not supported' in fcsvrCMD_STDOUT: self.logger('Archiving FCP Project assets is not supported!','error') else: self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDString)
[docs] def restore(self,recurseProductions=False): '''Restores the loaded entity. If this is a production, then we will restore all assets which are members of the production. If recurseProductions is true, then we will restore it's members as well. ''' ## If this is a production, restore all members of the production. if self.entityType == 'project': if recurseProductions: members = self.productionMemberAddresses() else: members = self.productionAssetAddresses() ## Iterate through our members and restore them for member in members: memberObj = FCSVRClient(entityPath=member) memberObj.loadConfiguration(self.configParser) memberObj.restore(recurseProductions=recurseProductions) ## If this is an asset, simply restore it. elif self.entityType == 'asset': ## Restore the asset ## Basic sanity checks. if not self.entityID: msg = 'Could not restore entity, no entityID is loaded.' self.logger(msg,'error') raise FCSError(msg) ## Run our fcsvr_client command. if not os.geteuid() == 0: if not self.useSudo: self.logger('Could not restore entity: root privileges are required!','error') raise FCSProductionLoadError("Restoring entities requires root privileges!") useSudo = True else: useSudo = False if useSudo: fcsvrCMDString = ('/usr/bin/sudo "%s" restore %s' % (self.pathToFCSVRClient,self.entityPath())) else: fcsvrCMDString = ('"%s" restore %s' % (self.pathToFCSVRClient,self.entityPath())) self.logger('fcsvrCMD:\n %s' % fcsvrCMDString,'debug') fcsvrCMD = subprocess.Popen(fcsvrCMDString,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("fcsvr_client output: %s %s" % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR),'debug') if not fcsvrCMD.returncode == 0: ##self.logger("FCSVRClient Error: %s %s" % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR),'error') ##return False try: return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDString) except FCSVRClientFileError: self.logger('Could not restore asset, no file is not available on ' 'archive device!','error') return False return True
[docs] def analyze(self,force=False,FCP=False,recurseProductions=False): '''Analyzes the loaded entity.''' ## If this is a production, analyze all members of the production. if self.entityType == 'project': if recurseProductions: members = self.productionMemberAddresses() else: members = self.productionAssetAddresses() ## Iterate through our members and analyze them for member in members: memberObj = FCSVRClient(entityPath=member) memberObj.loadConfiguration(self.configParser) memberObj.analyze(force=force,FCP=FCP) ## If this is an asset, simply restore it. elif self.entityType == 'asset': ## Analyze the asset ## Basic sanity checks. if not self.entityID: msg = 'Could not analyze entity, no entityID is loaded.' self.logger(msg,'error') raise FCSError(msg) ## Run our fcsvr_client command. cmdOpts = '' if force: cmdOpts += ' --force' if FCP: cmdOpts += ' --fcp' fcsvrCMDString = ('"%s" analyze %s %s' % (self.pathToFCSVRClient,cmdOpts,self.entityPath())) self.logger('fcsvrCMD:\n %s' % fcsvrCMDString,'debug') fcsvrCMD = subprocess.Popen(fcsvrCMDString,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("fcsvr_client output: %s %s" % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR),'debug') if not fcsvrCMD.returncode == 0: self.logger("FCSVRClient Error: %s %s" % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR),'error') return False
[docs] def delete(self): '''Deletes the loaded entity''' if not self.entityType or not self.entityID: self.logger("Could not delete entity, entityType or entityID not set!",'error') return False ## Delete the asset ## Run our fcsvr_client command. if not os.geteuid() == 0: if not self.useSudo: self.logger('Could not delete entity: root privileges are required!','error') raise FCSProductionLoadError("Deleting entities requires root privileges!") useSudo = True else: useSudo = False if useSudo: fcsvrCMDString = '/usr/bin/sudo "%s" delete --confirm /%s/%s' % (self.pathToFCSVRClient, self.entityType, self.entityID) else: fcsvrCMDString = '"%s" delete --confirm /%s/%s' % (self.pathToFCSVRClient, self.entityType, self.entityID) self.logger('fcsvrCMD:\n %s' % fcsvrCMDString,'debug') fcsvrCMD = subprocess.Popen('%s' % fcsvrCMDString,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("fcsvr_client output: %s %s" % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR),'debug') if not fcsvrCMD.returncode == 0: self.logger("FCSVRClient Error: %s %s" % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR),'error') return False
[docs] def createAssetFromFSPath(self,path,deviceName="",deviceID="",mdSet="",relPath="",overwriteExistingFiles="ignore",setMD=True,backgroundAnalyze=True): '''This function takes a filesystem path and will create an asset from the file. Can be passed a deviceName or deviceID, metadata set, and an optional relative path to be uploaded. An option overwriteExistingFiles determines behavior if a file already exists, options are 'ignore','iterate', and 'overwrite' ''' ## Make sure our asset exists if not os.path.exists(path): self.logger("Cannot create asset, no file could be found at path: '%s'" % path) return False assetPath = path assetName = os.path.basename(assetPath) if not mdSet: mdSet = self.defaultAssetMetadataSet ## Fetch our device myDeviceDict = {} if not deviceName and not deviceID: self.logger("Cannot create asset, deviceName or deviceID not specified!",'error') return False if deviceName: myDeviceDict = self.deviceWithName(deviceName) if not myDeviceDict: self.logger("Could not find device with name: %s!" % deviceName,'error') return False if deviceID: if myDeviceDict: if not myDeviceDict["DEVICE_ID"]: self.logger("Device with name: %s has a deviceID: %s, which conflicts with specified deviceID: %s, cannot continue" % (deviceName,myDeviceDict["DEVICE_ID"],deviceID),'error') return False deviceName = myDeviceDict["DEVICE_NAME"] else: myDeviceDict = self.deviceWithID(deviceID) ## Construct or path targetDir = myDeviceDict["FSPATH"] fcsDir = myDeviceDict["DESC_DEVICE_ADDRESS"] if not os.path.exists(targetDir): self.logger("Path: %s for Device: %s does not exist, cannot continue!" %(targetDir,assetDeviceName),'error') return False ## If we have a relative path, clean it up and append it if relPath: ## Replace some problematic characters, this probably needs to be greatly expanded relPath = relPath.replace("`","'").replace('"',"'") if relPath[0:1] == "/": relPath = relPath[1:] targetDir = os.path.join(targetDir,relPath) fcsDir = os.path.join(fcsDir,relPath) ## At this point we have passed validation, announce our presence self.logger("Creating asset %s from filesystem path: '%s' to device:'%s' fcspath:'%s' "%(mdSet,assetPath,deviceName,fcsDir)) ## Create our directory if it doesn't exist if not os.path.exists(targetDir): theDir = targetDir try: while not os.path.exists(targetDir): theDir = targetDir while not os.path.exists(theDir): if not os.path.exists(theDir) and os.path.exists(os.path.dirname(theDir)): self.logger("Creating directory:%s with userID:%s" % (theDir,self.FCSUID),'debug') os.mkdir(theDir) os.chown(theDir,self.FCSUID,-1) break else: theDir = os.path.dirname(theDir) except: self.logger("Error creating directory: %s, cannot continue!" % theDir,'error') return False ## If the target already exists, act based upon overwriteExistingFiles targetPath = os.path.join(targetDir,assetName) fcsPath = os.path.join(fcsDir,assetName) copyFile = True if os.path.exists(targetPath): if overwriteExistingFiles.lower() == "ignore": self.logger("File exists at path:'%s', skipping copy" % targetPath) copyFile = False elif overwriteExistingFiles.lower() == "iterate": iterCount = 1 fileBase,fileExtension = os.path.splitext(assetName) while os.path.exists(targetPath): targetPath = os.path.join(targetDir,"%s-%s%s" % (fileBase,iterCount,fileExtension)) fcsPath = os.path.join(fcsDir,"%s-%s%s" % (fileBase,iterCount,fileExtension)) iterCount += 1 elif overwriteExistingFiles.lower() == "replace": self.logger("Found existing file at path:'%s', removing!") shutil.rmtree(targetPath) else: self.logger("Found existing object at path:'%s'," % targetPath() + " and invalid overwriteExistingFiles option:%s" % overwriteExistingFiles + " was specified. (expects 'ignore','iterate', or 'replace'), cannot continue" ,'error') return False if copyFile: ## Copy our file self.logger(" - Copying File to '%s'" % targetPath) shutil.copy(assetPath,targetPath) ## Set proper ownership self.logger(" - Updating ownership to UID:%s" % self.FCSUID) os.chown(targetPath,self.FCSUID,-1) self.logger(" - Copy Finished, creating asset") ## URLEncode our fcsPath ## Todo: add more logic to ensure we don't double quote. qoutedFCSPath = urllib.quote(fcsPath) if self.fcsvr_client_createasset(qoutedFCSPath, mdSet, backgroundAnalyze=backgroundAnalyze, setMD=setMD): self.logger("Successfully Created Asset /asset/%s" % self.entityID) return True else: return False
[docs] def createThumbnail(self,newThumbnailPath,assetID="",deviceName="",deviceID="",mdSet=""): '''This is accomplished by creating an asset for the thumbnail, getting it's thumbnail path, and then creating the link''' linkType = 5 ## Make sure the replacement file exists: if not os.path.isfile(newThumbnailPath): if os.path.exists(newThumbnailPath): self.logger("Non-file object exists at path:%s" % newThumbnailPath,'error') return False else: self.logger("File does not exist at path:%s" % newThumbnailPath,'error') return False if not assetID: if self.entityID and self.entityType == "asset": assetID = self.entityID if not deviceName: if self.thumbnailDeviceName: deviceName = self.thumbnailDeviceName if not deviceName: self.logger("Cannot create thumbnail, no device was specified!",'error') return False if not mdSet: mdSet = self.defaultThumbnailMetadataSet #### Create the asset ## first, get our desired device path myDeviceDict = self.deviceWithName(deviceName) if not myDeviceDict: self.logger("Could not find device with name: %s, cannot continue!" % deviceName,'error') return False ## Determine our target location. targetDir = myDeviceDict["FSPATH"] fcsDir = myDeviceDict["DESC_DEVICE_ADDRESS"] ## Get our thumbnailname thumbnailName = os.path.basename(newThumbnailPath) thumbnailBaseName,thumbnailExtension = os.path.splitext(thumbnailName) targetName = thumbnailName i = 1 while os.path.exists(os.path.join(targetDir,targetName)): targetName = "%s_%s%s" % (thumbnailBaseName,i,thumbnailExtension) i += 1 targetPath = os.path.join(targetDir,targetName) fcsPath = os.path.join(fcsDir,targetName) ## Copy the file try: shutil.copy(newThumbnailPath,targetPath) ## Set proper ownership os.chown(targetPath,self.FCSUID,-1) except: self.logger("Error copying file:'%s' to destination:'%s'!, cannot create thumbnail" % (newThumbnailPath,targetPath),'error') return False ## Import our asset into FCS, this will also update the asset's MD self.logger("Importing thumbnail at FCS Path:'%s' FS Path:'%s'" % (fcsPath,targetPath),'debug') ## Create an FCSVRClient object for our thumbnail myFCSVRClient = FCSVRClient() ##myFCSVRClient.fields = self.fields if not myFCSVRClient.fcsvr_client_createasset(fcsPath=fcsPath,mdSet=mdSet,backgroundAnalyze=False,setMD=False): self.logger("Error creating asset: %s" % myFCSVRClient.lastError,'error') return False time.sleep(5) ## Get the thumbnail for our thumbnail createdFCSThumbnailPath = myFCSVRClient.getFCSPathForLinkType(linkType=linkType) if not createdFCSThumbnailPath: self.logger("Could not find thumbnail reference for imported thumbnail file!",'error') return False ## Create our link self.fcsvr_client_make_link(linkType,childPath=createdFCSThumbnailPath,moveLink=True) ## Delete the thumbnail asset myFCSVRClient.delete() ##fcsvrCMD = subprocess.Popen("'%s' make_link --linktype %s --movelink /asset/%s %s" % (self.pathToFCSVRClient,linkType,self.entityID,createdFCSThumbnailPath),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) ##fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() ##self.logger("fcsvrCMD:\n fcsvr_client make_link --linktype %s --movelink /asset/%s %s" % (linkType,self.entityID,createdFCSThumbnailPath),'debug') #if not fcsvrCMD.returncode == 0: #self.logger("%s" % fcsvrCMD_STDOUT,'error') #return False return True
[docs] def replaceThumbnail(self,newThumbnailPath,assetID=""): '''Method which will replace the thumbnail file for the specified/stored assetID with file provided at newThumbnailPath. ''' ## Make sure the replacement file exists: if not os.path.isfile(newThumbnailPath): if os.path.exists(newThumbnailPath): self.logger("Non-file object exists at path:%s" % newThumbnailPath,'error') return False else: self.logger("File does not exist at path:%s" % newThumbnailPath,'error') return False if not assetID: if self.entityID and self.entityType == "asset": assetID = self.entityID ## Get our old thumbnail path: thumbnailPath = self.getThumbnailPath(assetID=assetID) ## Copy our thumbnail over the existing thumbnail try: shutil.copy(newThumbnailPath,thumbnailPath) os.chown(thumbnailPath,self.FCSUID,-1) except: self.logger("Error copying new thumbnail from: %s to %s" % (newThumbnailPath,thumbnailPath),'error') return False return True
[docs] def generateSearchXML(self,fields,match='exact'): '''Generates an XML file for use by fcsvr_client search --xmlcrit, should be past a list of FCSXMLFields (with their appropriate values set). Returns an absolute path to the XML file.''' ## Generate a string of our field names fieldNames = "" for field in fields: if not fieldNames: fieldNames = field else: fieldNames += ", %s" % field self.logger('generateSearchXML() Generating search XML with fields:%s' ' match type:%s' % (fieldNames,match),'debug') ## create our new xml doc, add our root FCS elements: ## <?xml version="1.0"?> ## <session> ## <values> ## <value id="CRIT_TYPE"> ## <int>3</int> ## </value> ## <value id="CRIT_INTERSECT"> ## <valuesList> ## <values> ## <value id="CRIT_CMP_VALUE"> ## <value id="CUST_TITLE"> ## <string>02-Ducati-Camera A-Color</string> ## </value> ## </value> ## <value id="CRIT_CMP_OP"> ## <atom>eq</atom> ## </value> ## <value id="CRIT_TYPE"> ## <int>1</int> ## </value> ## </values> ## </valuesList> ## </value> ## </values> ## </session> xmlDoc = minidom.Document() fcsElement = xmlDoc.createElement('session') xmlDoc.appendChild(fcsElement) valuesElement = xmlDoc.createElement('values') fcsElement.appendChild(valuesElement) ## <value id="CRIT_TYPE"> ## <int>3</int> ## </value> critTypeElement = xmlDoc.createElement('value') critTypeElement.setAttribute('id', 'CRIT_TYPE') critTypeValueElement = xmlDoc.createElement('int') critTypeValueNode = xmlDoc.createTextNode('3') critTypeValueElement.appendChild(critTypeValueNode) critTypeElement.appendChild(critTypeValueElement) valuesElement.appendChild(critTypeElement) ## <value id="CRIT_INTERSECT"> ## <valuesList> ## <values> ## <value id="CRIT_CMP_VALUE"> critIntersectElement = xmlDoc.createElement('value') critIntersectElement.setAttribute('id', 'CRIT_INTERSECT') valuesListElement = xmlDoc.createElement('valuesList') critIntersectElement.appendChild(valuesListElement) critIntersectValuesElement = xmlDoc.createElement('values') valuesListElement.appendChild(critIntersectValuesElement) critCMPValueElement = xmlDoc.createElement('value') critCMPValueElement.setAttribute('id', 'CRIT_CMP_VALUE') critIntersectValuesElement.appendChild(critCMPValueElement) valuesElement.appendChild(critIntersectElement) ## Iterate through our passed field for XML and add our individual field ## criteria. for field in fields: try: fieldName = field.name dbname = field.dbname if not fieldName and not dbname: self.logger('xmlOut() found field with no fieldname or dbname! skipping!','error') continue if not fieldName and dbname: fieldName = self.fieldNameForDBFieldName(dbname) field.name = fieldName self.logger('xmlOut() fieldName not set for field:%s, resolved:%s' % (dbname,fieldName),'debug') elif not dbname and fieldName: dbname = self.dbFieldNameForFieldName(fieldName) field.dbname = dbname self.logger('xmlOut() dbname not set for field:%s, resolved:%s' % (fieldName,dbname),'debug') if dbname and field.value: theFieldElement = xmlDoc.createElement('value') theFieldElement.setAttribute('id', field.dbname) ## generate our dataType specific XML nodes, if no ## dataType is set, assume string if field.dataType[0:6] == 'string' or not field.dataType: fieldSourceElement = xmlDoc.createElement('string') fieldSourceElement.setAttribute('xml:space','preserve') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'dateTime': fieldSourceElement = xmlDoc.createElement('timestamp') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'timecode': fieldSourceElement = xmlDoc.createElement('timecode') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'float': fieldSourceElement = xmlDoc.createElement('real') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'int64': fieldSourceElement = xmlDoc.createElement('bigint') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif (field.dataType == 'int' or field.dataType == 'integer'): fieldSourceElement = xmlDoc.createElement('int') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'fraction': fieldSourceElement = xmlDoc.createElement('fraction') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'coords': fieldSourceElement = xmlDoc.createElement('intpair') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'bool': fieldSourceElement = xmlDoc.createElement('bool') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) else: self.logger('Unknown dataType: \'%s\'' % field.dataType,'error') continue theFieldElement.appendChild(fieldSourceElement) ## Append our field element to our "values" element i.e. ## <values> ## <value id="MYFIELD"> ## <bool>true</bool> critCMPValueElement.appendChild(theFieldElement) del theFieldElement except: theErrorMsg = 'An error occured processing field: \'%s\', skipping!' % field.name self.logger(theErrorMsg,'error') ## <value id="CRIT_CMP_OP"> ## <atom>eq</atom> ## </value> critCMPOPElement = xmlDoc.createElement('value') critCMPOPElement.setAttribute('id','CRIT_CMP_OP') critCMPAtomElement = xmlDoc.createElement('atom') if match == 'exact': critCMPAtomElement.appendChild(xmlDoc.createTextNode('eq')) else: critCMPAtomElement.appendChild(xmlDoc.createTextNode('contains')) critCMPOPElement.appendChild(critCMPAtomElement) critIntersectValuesElement.appendChild(critCMPOPElement) ## <value id="CRIT_TYPE"> ## <int>1</int> ## </value> critTypeElement = xmlDoc.createElement('value') critTypeElement.setAttribute('id','CRIT_TYPE') critTypeIntElement = xmlDoc.createElement('int') critTypeIntElement.appendChild(xmlDoc.createTextNode('1')) critTypeElement.appendChild(critTypeIntElement) critIntersectValuesElement.appendChild(critTypeElement) ## We have finished building our fields, at this point, write out our ## XML tempFileHandle,tempFilePath = tempfile.mkstemp(dir=self.supportDir, suffix="_search.xml") self.logger("generateSearchXML() Using temp file: %s" % tempFilePath,'debug') os.fdopen(tempFileHandle).close() theFile = codecs.open(tempFilePath, 'w','utf-8') xmlDoc.writexml(theFile) theFile.close() return tempFilePath
[docs] def generateTempXMLFile(self): '''generates a temporary file constructed from self.fields, returns the file path''' if not self.fields: self.logger("No fields are set, cannot generate xml file!",'error') return False ## Create a temporary directory tempFileHandle,tempFilePath = tempfile.mkstemp(dir=self.supportDir,suffix="_%s%s.xml" % (self.entityType,self.entityID)) ## Generate our XML,write to our temp file if not self.xmlOut(tempFilePath): self.logger("No XML was generated",'error') tempFileHandle.close() tempFilePath = False ## Close out our temp file. try: ## Fetch and close our file descriptor fd = os.fdopen(tempFileHandle) fd.close() except: pass return tempFilePath
[docs] def setMD(self): '''If we have a valid entityType and ID, we will attempt to run an fcsvr_client --setmd operation. This is performed by writing a temporary XML file and reading it in with fcsvr_client --xml setmd''' ## Setting metadata with fcsvr_client requires root access. Make sure we're ## running under effective UID 0, or that we have useSudo set (requires ## modified sudoers file). if not os.geteuid() == 0: if not self.useSudo: self.logger("Could not set Metadata",'error') raise RuntimeError("Updating metadata requires root privileges!") useSudo = True else: useSudo = False if not self.entityType or not self.entityID: self.logger("Could not create entity, id or type is not set!" % entityType,'error') raise RuntimeError("Could not create entity, id or type is not set!") tempFilePath = self.generateTempXMLFile() if not tempFilePath: return False ## Run our fcsvr_client command. if useSudo: fcsvrCMDTXT = "/usr/bin/sudo '%s' setmd /%s/%s --xml '%s'" % (self.pathToFCSVRClient,self.entityType,self.entityID,tempFilePath) else: fcsvrCMDTXT = "'%s' setmd /%s/%s --xml '%s'" % (self.pathToFCSVRClient,self.entityType,self.entityID,tempFilePath) fcsvrCMD = subprocess.Popen("%s" % fcsvrCMDTXT,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger("fcsvr_client command: %s" % "%s" % fcsvrCMDTXT,'debug') self.logger("fcsvr_client output: %s tempfilepath: %s" % (fcsvrCMD_STDOUT,tempFilePath),'debug') ## Remove the temp file if not self.keepFiles: os.remove(tempFilePath) if not fcsvrCMD.returncode == 0: self.logger("%s %s" % (fcsvrCMD_STDERR,fcsvrCMD_STDOUT),'error') return False return True
[docs] def xmlOut(self,filePath): '''Output our XML to the specified file''' if not self.entityType: self.logger('Could not create entity, entityType is not set!' % entityType,'error') return False if not self.entityType in self.registeredEntities: self.logger('Could not set metadata, entityType:% is not valid!' % entityType,'error') return False if not self.entityID > 0: self.logger('xmlOut() entityID not set! Cannot generate XML.', 'warning') if not len(self.fields) > 0: self.logger('xmlOut() no fields set! Cannot generate XML.', 'error') return False if not os.path.exists(filePath) and not os.path.isdir(os.path.dirname(filePath)): self.logger('xmlOut() could not create file at %s' % filePath) return False if os.path.exists(filePath) and not self.overwriteExistingFiles: self.logger('xmlOut() file already exists at %s' % filePath) return False ## create our new xml doc, add our root FCS elements: ## <?xml version="1.0"?> ## <session> ## <values> ## <value id="CUST_DESCRIPTION"> ## <string xml:space="preserve">this is my new description</string> ## </value> ## </values> ##</session> xmlDoc = minidom.Document() fcsElement = xmlDoc.createElement('session') xmlDoc.appendChild(fcsElement) valuesElement = xmlDoc.createElement('values') fcsElement.appendChild(valuesElement) ## And then our individual fields. for field in self.fields.itervalues(): try: fieldName = field.name dbname = field.dbname if not fieldName and not dbname: self.logger('xmlOut() found field with no fieldname or dbname! skipping!','error') continue if not fieldName and dbname: fieldName = self.fieldNameForDBFieldName(dbname) field.name = fieldName self.logger('xmlOut() fieldName not set for field:%s, resolved:%s' % (dbname,fieldName),'debug') elif not dbname and fieldName: dbname = self.dbFieldNameForFieldName(fieldName) field.dbname = dbname self.logger('xmlOut() dbname not set for field:%s, resolved:%s' % (fieldName,dbname),'debug') if dbname and field.value: theFieldElement = xmlDoc.createElement('value') theFieldElement.setAttribute('id', field.dbname) ## generate our dataType specific XML nodes, if no ## dataType is set, assume string if field.dataType[0:6] == 'string' or not field.dataType: fieldSourceElement = xmlDoc.createElement('string') fieldSourceElement.setAttribute('xml:space','preserve') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'dateTime': fieldSourceElement = xmlDoc.createElement('timestamp') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'timecode': fieldSourceElement = xmlDoc.createElement('timecode') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'float': fieldSourceElement = xmlDoc.createElement('real') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'int64': fieldSourceElement = xmlDoc.createElement('bigint') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif (field.dataType == 'int' or field.dataType == 'integer'): fieldSourceElement = xmlDoc.createElement('int') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'fraction': fieldSourceElement = xmlDoc.createElement('fraction') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'coords': fieldSourceElement = xmlDoc.createElement('intpair') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) elif field.dataType == 'bool': fieldSourceElement = xmlDoc.createElement('bool') theValueNode = xmlDoc.createTextNode(field.value) fieldSourceElement.appendChild(theValueNode) else: self.logger('Unknown dataType: \'%s\'' % field.dataType,'error') continue theFieldElement.appendChild(fieldSourceElement) ## Append our field element to our "values" element i.e. ## <values> ## <value id="MYFIELD"> ## <bool>true</bool> valuesElement.appendChild(theFieldElement) del theFieldElement except: theErrorMsg = 'An error occured processing field: \'%s\', skipping!' % field.name self.logger(theErrorMsg,'error') ## We have finished building our fields, at this point, write out our ## XML theFile = open(filePath, 'w') xmlDoc.writexml(theFile) theFile.close() return True
[docs] def getFilePath(self): '''Returns the file path to the primary representation for the loaded asset''' deviceDict = self.deviceWithName(self.valueForField('Stored On')) location = self.valueForField('Location') fileName = self.valueForField('File Name') self.logger('getFilePath() Found deviceDict: %s' % deviceDict,'debug') deviceFSPath = deviceDict['FSPATH'] ## If our location has a leading /, remove it if location[0:1] == "/": location = location[1:] assetPath = os.path.join(deviceFSPath,location,fileName) return assetPath
[docs] def getFilePathForThumbnail(self,id='',xmlDOM=''): '''Returns the filesystem thumbnail path for asset given ID, loads from fcsvr_client''' if not id: if self.entityID and self.entityType == 'asset': assetID = self.entityID else: self.logger('Asset ID was not provided and is not stored, cannot continue!','error') raise RuntimeError('Asset ID was not provided and is not stored, cannot continue!') else: assetID = id self.logger('Determining thumbnailpath for id:%s' % id,'debug') ## If our assetID lookup matches our entityID and we have a cached value, return it if assetID == self.entityID and self.thumbnailPath and not xmlDOM: return self.thumbnailPath ## if we have an xmlDOM, use it, otherwise grab it from our object if not xmlDOM: xmlDOM = self.getParentLinksXML(id=assetID) if not xmlDOM: self.logger('Could not generate xml!','error') return False thumbnailPath = self.getFSPathForLinkType(linkType=5,id=assetID,xmlDOM=xmlDOM) if not thumbnailPath: self.logger('Could not determine thumbnail path for asset:%s' % assetID,'error') return False ## If our assetID lookup matches our entityID, cache our value if assetID == self.entityID and thumbnailPath: self.thumbnailPath = thumbnailPath return thumbnailPath
[docs] def getFilePathForProxy(self,id='',xmlDOM=''): '''Returns filesystem path to the associated proxy file''' if not id: if self.entityID and self.entityType == 'asset': entityID = self.entityID else: self.logger('Asset ID was not provided and is not stored, cannot continue!','error') raise RuntimeError('Asset ID was not provided and is not stored, cannot continue!') else: entityID = id if not entityID: self.logger('assetID is not set, cannot determine proxyPath','error') return False ## If our assetID lookup matches our entityID and we have a cached value, return it if entityID == self.entityID and self.proxyPath and not xmlDOM: return self.proxyPath ## if we have an xmlDOM, use it, otherwise grab it from our object if not xmlDOM: xmlDOM = self.getParentLinksXML(id=entityID) if not xmlDOM: self.logger('Could not generate xml!','error') return False proxyPath = self.getFSPathForLinkType(linkType=4,id=entityID,xmlDOM=xmlDOM) if not proxyPath: self.logger('Could not determine proxy path for asset:%s' % entityID,'error') return False ## If our assetID lookup matches our entityID, cache our value if entityID == self.entityID and proxyPath: self.proxyPath = proxyPath return proxyPath
[docs] def getFilePathForPosterFrame(self,id='',xmlDOM=''): '''Returns filesystem path to the associated poster frame''' if not id: if self.entityID and self.entityType == 'asset': assetID = self.entityID else: self.logger('Asset ID was not provided and is not stored, cannot continue!','error') raise RuntimeError('Asset ID was not provided and is not stored, cannot continue!') else: assetID = id ## If our assetID lookup matches our entityID and we have a cached value, return it if assetID == self.entityID and self.posterFramePath and not xmlDOM: return self.posterFramePath ## if we have an xmlDOM, use it, otherwise grab it from our object if not xmlDOM: xmlDOM = self.getParentLinksXML(id=assetID) if not xmlDOM: self.logger('Could not generate xml!','error') return False posterFramePath = self.getFSPathForLinkType(linkType=6,id=assetID,xmlDOM=xmlDOM) if not posterFramePath: self.logger('Could not determine poster frame path for asset:%s' % assetID,'error') return False ## If our assetID lookup matches our entityID, cache our value if assetID == self.entityID and posterFramePath: self.posterFramePath = posterFramePath return posterFramePath
[docs] def getFilePathForEditProxy(self,id='',xmlDOM=''): '''Returns filesystem path to the associated thumbnail''' if not id: if self.entityID and self.entityType == 'asset': assetID = self.entityID else: self.logger('Asset ID was not provided and is not stored, cannot continue!','error') raise RuntimeError('Asset ID was not provided and is not stored, cannot continue!') else: assetID = id ## If our assetID lookup matches our entityID and we have a cached value, return it if assetID == self.entityID and self.editProxyPath and not xmlDOM: return self.editProxyPath ## if we have an xmlDOM, use it, otherwise grab it from our object if not xmlDOM: xmlDOM = self.getParentLinksXML(id=assetID) if not xmlDOM: self.logger('Could not generate xml!','error') return False editProxyPath = self.getFSPathForLinkType(linkType=5,id=assetID,xmlDOM=xmlDOM) if not editProxyPath: self.logger('Could not determine edit proxy path for asset:%s' % assetID,'error') return False ## If our assetID lookup matches our entityID, cache our value if assetID == self.entityID and editProxyPath: self.editProxyPath = editProxyPath return editProxyPath
[docs] def initWithProductionTitle(self,title,match='exact'): '''Inits a production based on Title''' ## lookup our production with the requested title theProduction = self.productionWithTitle(title,match=match) self.entityID = theProduction.entityID self.entityType = theProduction.entityType self.entityTitle = theProduction.entityTitle self.entityMetadataSet = theProduction.entityMetadataSet self.fields = theProduction.fields return True
[docs] def initWithProductionID(self,productionID): '''Inits a production based on ID''' ## Set our id and type self.entityID = productionID self.entityType = 'project' ## Make sure we have a productionID, if not, bail if not productionID: raise FCSObjectLoadError('Could not init production: no production id was' ' provided') ## delete our stored fields if self.fields: self.fields = {} ## Get our project metadata cmdString = 'getmd "/project/%s" --xml' % productionID ## todo: add timeout to detect if FCSVR is down fcsvrCMD = subprocess.Popen('"%s" %s' % (self.pathToFCSVRClient,cmdString), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvr_client command: fcsvr_client %s' % cmdString,'debug') if not fcsvrCMD.returncode == 0: self.logger('ERROR:%s' % fcsvrCMD_STDOUT,'error') return False ##self.logger("fcsvr_client output: %s fcsPath: %s" % (fcsvrCMD_STDOUT,self.pathToFCSVRClient),'debug') ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client command: fcsvr_client %s' % cmdString,'error') return False self.dom = myDom #try: sessionRoot = myDom.childNodes[0] searchResultCount = 0 resolvedFileName = '' resolvedMetadataSet = '' resolvedTitle = '' resolvedProjectID = '' resolvedDBEntityID = '' for value in sessionRoot.getElementsByTagName('value'): #self.logger('dbFieldNameForFieldName() rootValue nodename: %s' % rootValue.nodeName,'debug') valueID = value.attributes['id'].value #self.logger('dbFieldNameForFieldName() Searching value: %s' % valueID,'debug') if valueID == 'PROJECT_NUMBER': try: resolvedProjectID = value.childNodes[1].childNodes[0].data except: pass elif valueID == 'DB_ENTITY_ID': try: resolvedDBEntityID = value.childNodes[1].childNodes[0].data except: pass elif valueID == 'PROJECT_TYPE': resolvedMetadataSet = value.childNodes[1].childNodes[0].data elif valueID == 'CUST_TITLE': try: resolvedTitle = value.childNodes[1].childNodes[0].data except: resolvedTitle = '' if (not resolvedDBEntityID or resolvedDBEntityID == '0'): msg = ('Could not init production with id:%s, production does not exist!' % productionID) self.logger(msg,'error') raise FCSObjectLoadError(msg) self.logger('Finished init. projectID: %s, entityMetadataSet: %s,' ' entityTitle: %s DBEntityID: %s' % (resolvedProjectID, resolvedMetadataSet,resolvedTitle,resolvedDBEntityID) ,'debug') self.entityID = resolvedProjectID self.entityMetadataSet = resolvedMetadataSet self.entityTitle = resolvedTitle if self.entityID: return True
[docs] def initWithAssetID(self,assetID): '''Inits an asset based upon id''' ## Make sure we have an assetID, if not, bail if not assetID: raise FCSObjectLoadError('Could not init asset: no asset id was' ' provided') ## Get our asset metadata cmdString = 'getmd "/asset/%s" --xml' % assetID ## todo: add timeout to detect if FCSVR is down fcsvrCMD = subprocess.Popen('"%s" %s' % (self.pathToFCSVRClient,cmdString), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvr_client command: fcsvr_client %s' % cmdString,'debug') if not fcsvrCMD.returncode == 0: return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString='fcsvr_client %s' % cmdString) ##self.logger("fcsvr_client output: %s fcsPath: %s" % (fcsvrCMD_STDOUT,self.pathToFCSVRClient),'debug') ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client command: fcsvr_client %s' % cmdString,'error') return False self.dom = myDom #try: sessionRoot = myDom.childNodes[0] searchResultCount = 0 noMatch = False resolvedRelPath = '' resolvedDeviceID = '' resolvedDevice = '' resolvedFileName = '' resolvedAssetID = '' resolvedMetadataSet = '' resolvedTitle = '' resolvedDBEntityID = '' for value in sessionRoot.getElementsByTagName('value'): #self.logger('dbFieldNameForFieldName() rootValue nodename: %s' % rootValue.nodeName,'debug') valueID = value.attributes['id'].value ##self.logger('dbFieldNameForFieldName() Searching value: %s' % valueID,'debug') if valueID == 'CUST_LOCATION': try: resolvedRelPath = value.childNodes[1].childNodes[0].data if not resolvedRelPath or not resolvedRelPath == relPath: self.logger('Found Location: \'%s\' does not match our requested ' 'location:\'%s\',skipping search result!' % (resolvedRelPath,relPath),'debug') noMatch = True break; except: pass elif valueID == 'ASSET_NUMBER': resolvedAssetID = value.childNodes[1].childNodes[0].data elif valueID == 'DB_ENTITY_ID': try: resolvedDBEntityID = value.childNodes[1].childNodes[0].data except: pass elif valueID == 'PA_MD_CUST_FILENAME': resolvedFileName = value.childNodes[1].childNodes[0].data elif valueID == 'CUST_DEVICE': resolvedDeviceName = value.childNodes[1].childNodes[0].data resolvedDevice = self.deviceWithName(resolvedDeviceName) resolvedDeviceID = resolvedDevice['DEVICE_ID'] elif valueID == 'ASSET_TYPE': resolvedMetadataSet = value.childNodes[1].childNodes[0].data elif valueID == 'CUST_TITLE': try: resolvedTitle = value.childNodes[1].childNodes[0].data except: resolvedTitle = '' if not resolvedDBEntityID or resolvedDBEntityID == '0': msg = ('Could not init asset with id:%s, asset does not exist!' % assetID) self.logger(msg,'error') raise FCSEntityNotFoundError(entityType='asset',entityID=assetID) self.logger('Finished init. entityID: %s, entityMetadataSet: %s,' ' entityTitle: %s' % (resolvedAssetID,resolvedMetadataSet,resolvedTitle) ,'debug') self.deviceDict = resolvedDevice self.entityID = resolvedAssetID self.entityMetadataSet = resolvedMetadataSet self.entityTitle = resolvedTitle if self.entityID: return True
[docs] def initWithAssetTitle(self,title): '''Accepts a title in string form, and copies local data''' ## Get our asset with title. myAsset = self.assetWithTitle(title=title) self.entityID = myAsset.entityID self.entityType = myAsset.entityType self.entityTitle = myAsset.entityTitle self.entityMetadataSet = myAsset.entityMetadataSet self.fields = myAsset.fields return self
[docs] def initWithAssetFromFSPath(self,FSPath): '''Accepts a file system path, and loads basic information''' ## Get our FCSPath self.logger('initWithAssetFromFSPath() looking up fcsPath for path:%s' % FSPath,'debug') try: fcsPath = self.getFCSPathFromFSPath(FSPath) except: ## Attempt to resolve from an archive path onlinePath = self.getFSPathFromArchivePath(FSPath) fcsPath = self.getFCSPathFromFSPath(onlinePath) self.logger('initWithAssetFromFSPath() found fcsPath:%s for path:%s' % (fcsPath,FSPath),'debug') try: return self.initWithAssetFromFCSPath(fcsPath) except (FCSEntityNotFoundError,FCSVRClientError): raise FCSEntityNotFoundError(entityPath=FSPath)
[docs] def initWithAssetFromFCSPath(self,FCSPath): '''loads our object based upon a FCS path''' self.logger('initFromFCSPath() initing as asset from FCSPath:\'%s\'' % FCSPath,'debug') ## Extract the asset name from the path fileName = os.path.basename(FCSPath) ## Extract the devID and relPath from the path partialPath = os.path.dirname(FCSPath) try: match = re.match('^/dev/(\d+)/(.*)$',partialPath) deviceID = match.group(1) relPath = match.group(2) except: try: match = re.match('^/dev/(\d+)$',partialPath) deviceID = match.group(1) relPath = '' except: self.logger('Could not extract deviceID or partial path, path:\'%s\'' % partialPath) return False ## Perform a search for our asset cmdString = 'list_child_links %s --xml' % FCSPath ## todo: add timeout to detect if FCSVR is down fcsvrCMD = subprocess.Popen('"%s" %s' % (self.pathToFCSVRClient,cmdString), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvr_client command: fcsvr_client %s' % cmdString,'debug') ## IF fcsvrCMD didn't return quickly, consult with fcsvr_client_error ## to determine the problem. If it throws exception FCSClientFileError, ## then we raise a FCSClientEntityNotFoundError if not fcsvrCMD.returncode == 0: try: self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString='fcsvr_client %s' % cmdString) except FCSVRClientFileError: raise FCSEntityNotFoundError(entityPath=FCSPath) ##self.logger('fcsvr_client output: %s fcsPath: %s' % (fcsvrCMD_STDOUT,self.pathToFCSVRClient),'debug') ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client command: fcsvr_client %s' % cmdString,'error') return False self.dom = myDom #try: sessionRoot = myDom.childNodes[0] resolvedEntityPath = '' for value in sessionRoot.getElementsByTagName('value'): #self.logger('dbFieldNameForFieldName() rootValue nodename: %s' % rootValue.nodeName,'debug') valueID = value.attributes['id'].value ##self.logger('initWithAssetFromFCSPath() Searching value: %s' % valueID,'debug') if valueID == 'ADDRESS': try: resolvedEntityPath = value.childNodes[1].childNodes[0].data except: pass resolvedAssetID = resolvedEntityPath.split('/')[2] self.logger('initWithAssetID() found entityPath:%s, assetID:%s for FCS Address:%s' % (resolvedEntityPath,resolvedAssetID,FCSPath),'debug') return self.initWithAssetID(resolvedAssetID)
[docs] def old_initAssetFromFCSPath(self,FCSPath): '''loads our object based upon a FCS path''' self.logger("initFromFCSPath() initing as asset from FCSPath:'%s'" % FCSPath,'debug') ## Extract the asset name from the path fileName = os.path.basename(FCSPath) ## Extract the devID and relPath from the path partialPath = os.path.dirname(FCSPath) try: match = re.match('^/dev/(\d+)/(.*)$',partialPath) deviceID = match.group(1) relPath = match.group(2) except: try: match = re.match('^/dev/(\d+)$',partialPath) deviceID = match.group(1) relPath = '' except: self.logger("Could not extract deviceID or partial path, path:'%s'" % partialPath) return False ## Perform a search for our asset cmdString = 'search --crit "%s" /asset --xml' % fileName ## todo: add timeout to detect if FCSVR is down fcsvrCMD = subprocess.Popen('"%s" %s' % (self.pathToFCSVRClient,cmdString), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvr_client command: fcsvr_client %s' % cmdString,'debug') if not fcsvrCMD.returncode == 0: return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString='fcsvr_client %s' % cmdString) ##self.logger("fcsvr_client output: %s fcsPath: %s" % (fcsvrCMD_STDOUT,self.pathToFCSVRClient),'debug') ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client command: fcsvr_client %s' % cmdString,'error') return False self.dom = myDom #try: sessionRoot = myDom.childNodes[0] searchResultCount = 0 noMatch = False resolvedRelPath = '' resolvedDeviceID = '' resolvedFileName = '' resolvedAssetID = '' resolvedMetadataSet = '' resolvedTitle = '' for searchResult in sessionRoot.childNodes: #self.logger('dbFieldNameForFieldName() Searching nodename: %s' % searchResult.nodeName,'debug') if searchResult.nodeName == 'values': resolvedRelPath = '' resolvedDeviceID = '' resolvedFileName = '' resolvedAssetID = '' resolvedMetadataSet = '' resolvedTitle = '' noMatch = False for rootValue in searchResult.getElementsByTagName('value'): #self.logger('dbFieldNameForFieldName() rootValue nodename: %s' % rootValue.nodeName,'debug') rootValueID = rootValue.attributes['id'].value #self.logger('dbFieldNameForFieldName() Searching rootValueID: %s' % rootValueID,'debug') if rootValueID == 'COMPLETE': didComplete = rootValue.childNodes[1].childNodes[0].data if not didComplete: break; searchResultCount += 1 elif rootValueID == 'METADATA': noMatch = False for value in rootValue.getElementsByTagName('value'): valueID = value.attributes['id'].value ##self.logger('dbFieldNameForFieldName() Searching value: %s' % valueID,'debug') if valueID == 'CUST_LOCATION': try: resolvedRelPath = value.childNodes[1].childNodes[0].data if not resolvedRelPath or not resolvedRelPath == relPath: self.logger('Found Location: \'%s\' does not match our ' 'requested location:\'%s\',skipping search result!' % (resolvedRelPath,relPath),'debug') noMatch = True break; except: pass elif valueID == 'ASSET_NUMBER': resolvedAssetID = value.childNodes[1].childNodes[0].data elif valueID == 'PA_MD_CUST_FILENAME': resolvedFileName = value.childNodes[1].childNodes[0].data if not resolvedFileName or not resolvedFileName == fileName: self.logger('Found File Name: \'%s\' does not match our ' 'requested filename:\'%s\',skipping search result!' % (resolvedFileName,fileName),'debug') noMatch = True break elif valueID == 'CUST_DEVICE': resolvedDeviceName = value.childNodes[1].childNodes[0].data resolvedDevice = self.deviceWithName(resolvedDeviceName) resolvedDeviceID = resolvedDevice['DEVICE_ID'] try: if not int(resolvedDeviceID) == int(deviceID): self.logger('Found DeviceID: \'%s\' does not match our' 'requested Device ID:\'%s\',skipping search result!' % (resolvedDeviceID,deviceID),'debug') noMatch = True break except: self.logger('Error converting \'%s\' or \'%s\' to int!' % (resolvedDeviceID,deviceID),'debug') elif valueID == 'ASSET_TYPE': resolvedMetadataSet = value.childNodes[1].childNodes[0].data elif valueID == 'CUST_TITLE': try: resolvedTitle = value.childNodes[1].childNodes[0].data except: resolvedTitle = '' if not noMatch and resolvedAssetID: resolvedPath = u'%s' % os.path.join('/dev','%s' % resolvedDeviceID, resolvedRelPath, resolvedFileName) originalPath = u'%s' % FCSPath if resolvedPath == originalPath: self.deviceDict = resolvedDevice self.entityID = resolvedAssetID self.entityMetadataSet = resolvedMetadataSet self.entityTitle = resolvedTitle return True else: self.logger('Match failed! resolved path:\'%s\', requested path:\'%s\'' % (resolvedPath,FCSPath)) noMatch = True return False
[docs] def getFCSPathFromFSPath(self,FSPath): '''Resolves a device path from a filesystem path''' newPath = '' self.logger('getFCSPathFromFSPath() resolving path:%s' % FSPath,'debug') devicesMap = self.getDevicesMap() if not devicesMap or not len(devicesMap) > 0: self.logger('getFCSPathFromFSPath() devicesMap could not be loaded!','error') return False for deviceID,device in devicesMap.iteritems(): if FSPath[0:len(device['DEV_ROOT_PATH'])] == device['DEV_ROOT_PATH']: if device['DEVICE_TYPE'] == 'filesystem': newPath = os.path.join(device['DESC_DEVICE_ADDRESS'],FSPath[len(device['DEV_ROOT_PATH']) + 1:]) elif device['DEVICE_TYPE'] == 'contentbase': try: parentDirName = os.path.basename(os.path.dirname(FSPath)) decimalNameVal = int(parentDirName,16) newPath = os.path.join(device['DESC_DEVICE_ADDRESS'],'%s_%s' % (decimalNameVal,os.path.basename(FSPath))) except: self.logger('getFCSPathFromFSPath() Unexpected error converting contentbase path!','error') return False else: self.logger('getFCSPathFromFSPath() Unexpected DEVICE_TYPE:%s' % device['DEVICE_TYPE'],'error') return False break; self.logger('getFCSPathFromFSPath() resolved unquoted path:%s' % newPath,'debug') ## Encode FCS path with standard URL chars (i.e. ' ' = '%20') quotedPath = self.quoteString(newPath) self.logger('getFCSPathFromFSPath() resolved path:%s' % quotedPath,'debug') return quotedPath
[docs] def getFSPathFromFCSPath(self,fcsPath): '''Resolves a POSIX Path from an FCS path''' self.logger('getFSPathFromFCSPath() resolving fcsPath:%s' % fcsPath,'debug') ## Blow up the FCS Path pathArray = fcsPath.split('/') ##self.logger('pathArray:%s' % pathArray,'debug') newPath = '' ## Ensure it's a /dev path if not pathArray[1] == 'dev': self.logger('Passed path:%s does not seem to be a valid FCS path!' % fcsPath,'error') return False ## Abstract the device ID and dictionary deviceID = pathArray[2] self.logger('getFSPathFromFCSPath() found deviceID: %s' % pathArray[2],'debug') deviceDict = self.deviceWithID(deviceID) if not deviceDict: self.logger('Could not load Device for id:%s, cannot continue!' % deviceID,'debug') return False deviceFSPath = deviceDict['DEV_ROOT_PATH'] ## Get the remainder of the path: if deviceDict['DEVICE_TYPE'] == 'contentbase': if not len(pathArray) == 4: self.logger('Unexpected element count in path: %s for ContentBase ' 'device, expected 3 items, found %s ' % (fcsPath,len(pathArray)),'error') return False try: regexMatch = re.match('^(\d+)_(.*)$',pathArray[3]) fileID = int(regexMatch.group(1)) fileHexID = '%016X' % fileID fileName = regexMatch.group(2) ## Get Our subpaths from the hex substr path1 = fileHexID[12:14] path2 = fileHexID[10:12] newPath = os.path.join(deviceFSPath,path1,path2,fileHexID,fileName) except: self.logger('Could not determine fileID from filename:%s' % pathArray[3],'error') return False elif deviceDict['DEVICE_TYPE'] == 'filesystem': count=4 newPath = '' while count < len(pathArray): newPath = os.path.join(deviceFSPath,pathArray[count]) count += 1 else: self.logger('Unexpected DEVICE_TYPE encountered:%s' % deviceDict['DEVICE_TYPE'],'error') return False unQuotedPath = self.unquoteString(newPath) return unQuotedPath
[docs] def getFSPathFromArchivePath(self,archiveFilePath): '''Resolves a POSIX 'online' Path from a POSIX archive PATH i.e.: /MyArchiveDevicePath/4/myfile.mov -> /LibraryDevicePath/myfile.mov''' ## Determine our archive device myArchiveDevice = self.deviceWithPath(archiveFilePath) archiveBasePath = myArchiveDevice['FSPATH'] ## Determine the non-archive path for the file, extract the archive portion ## of the path and determine original location of the file. relFilePath = '' if archiveFilePath[0:len(archiveBasePath)] == archiveBasePath: ## First get our relative path extracted from our archive-relative path ## (/4/testfolder/myfile.mov) relFilePathTemp = archiveFilePath[len(archiveBasePath):] ## Extract the deviceID and save our device-relative path deviceID = relFilePathTemp.split('/')[1] relFilePath = relFilePathTemp[len('/%s' % deviceID):] else: self.logger('loadForFileAtPath() File Path:%s does not reside in Archive Path:%s','error') raise FCSObjectLoadError('File Path:%s does not exist in Archive Path:%s') ## Fetch our device info from our FCSVRClient object myDevice = self.deviceWithID(deviceID) ## Determine online asset path resolvedPath = u"%s%s" % (myDevice['FSPATH'],relFilePath) self.logger('loadForFileAtPath() determined relative path: %s deviceID: %s' ' resolvedPath: %s' % (relFilePath,deviceID,resolvedPath),'debug') self.logger('loadForFileAtPath() - device path:%s' % myDevice['FSPATH'],'debug') return resolvedPath
[docs] def quoteString(self,path): '''This function returns a quoted filepath acceptable for use by fcsvr_client''' ## Quote our string using urllib's quote function, skipping a few ## special escape characters. quotedString = urllib.quote(path,'&()/*!\'~$') quotedString = quotedString.replace('(','\(') quotedString = quotedString.replace(')','\)') quotedString = quotedString.replace('&','\&') quotedString = quotedString.replace('*','\*') quotedString = quotedString.replace('!','\!') quotedString = quotedString.replace("'","\\'") quotedString = quotedString.replace('~','\~') quotedString = quotedString.replace('$','\$') return quotedString
[docs] def unquoteString(self,path): '''This function returns a quoted filepath acceptable for use by fcsvr_client''' ## Quote our string using urllib's quote function, skipping a few ## special escape characters. unquotedString = urllib.unquote(path) unquotedString = unquotedString.replace('\(','(') unquotedString = unquotedString.replace('\)',')') unquotedString = unquotedString.replace('\&','&') unquotedString = unquotedString.replace('\*','*') unquotedString = unquotedString.replace('\!','!') unquotedString = unquotedString.replace("\'","'") unquotedString = unquotedString.replace('\~','~') unquotedString = unquotedString.replace('\$','$') return unquotedString
[docs] def loadFromFCSXMLObject(self,fcsXMLObject): '''Loads our object from values stored in fcsXMLObject''' if fcsXMLObject.entityID: self.entityID = fcsXMLObject.entityID if fcsXMLObject.entityType: self.entityType = fcsXMLObject.entityType if len(fcsXMLObject.fields) > 0: self.fields = fcsXMLObject.fields ## Attempt to load to verify that the entity exists. An exception ## will be thrown if we fail. myFCSVRClient = FCSVRClient() if self.entityType == 'project': myFCSVRClient.initWithProductionID(self.entityID) else: myFCSVRClient.initWithAssetID(self.entityID)
[docs] def getValueForLinkType(self,value,linkType,xmlDOM='',id='',type='',origin='parent'): '''Returns the value of <value> node with id of value and LINK_TYPE of linkType. If xmlDOM is provided, we will search it, otherwise we will generate our own xmlDOM for the search using provided or stored values of entityID and entityType. Argument 'origin' can be provided to denote whether we are fetching parent or child links.''' ## Assign new var (for readability) requestedValue = value if not xmlDOM: if not id and self.entityID: entityID = self.entityID elif not id: self.logger('Could not determine ID to search!'); raise RuntimeError('Could not determine ID to search!') else: entityID = id entityType = type if origin == 'child': xmlDOM = self.getChildLinksXML(id=entityID,type=entityType) else: xmlDOM = self.getParentLinksXML(id=entityID,type=entityType) ## Do our work on the DOM matchedEntries = [] ## List of matching dicts matchedValues = [] ## List of specific matching values linkAddress = '' for myValues in xmlDOM.getElementsByTagName('values'): loopDied = False assetDict = {} for myValue in myValues.getElementsByTagName('value'): valueID = myValue.attributes['id'].value ##self.logger('VALUE ID ATTRIB:%s' % valueID,'debug') if valueID == 'LINK_TYPE': ## Check to see if it's the thumbnail (LINK_TYPE of 5) assetDict['LINK_TYPE'] = int(myValue.childNodes[1].childNodes[0].data) if not assetDict['LINK_TYPE'] == linkType: loopDied = True assetDict = {} break if valueID == requestedValue: assetDict[requestedValue] = myValue.childNodes[1].childNodes[0].data if (not loopDied and 'LINK_TYPE' in assetDict and assetDict['LINK_TYPE'] == linkType and requestedValue in assetDict): self.logger("Found matched value:'%s' (linkType:%s RequestedValue:'%s')" % (requestedValue, assetDict['LINK_TYPE'], assetDict[requestedValue]) ,'debug') matchedEntries.append(assetDict) matchedValues.append(assetDict[requestedValue]) if not len(matchedValues) > 0: ##raise FCSError("Could not find entries with VALUE:%s LINK_TYPE:%s" ## " in XML object!" % (requestedValue,linkType)) return [] else: self.logger('Found %s matching entries for LINK_TYPE:%s in XML object!' % (len(matchedValues),linkType),'debug') return matchedValues
[docs] def getFCSPathForLinkType(self,linkType,id='',xmlDOM=''): '''Returns a Final Cut server relative path for linked assets''' entityID = False if not id: if self.entityID and self.entityType == 'asset': entityID = self.entityID else: entityID = id if not entityID: self.logger('Could not determine ID for asset, cannot continue!','error') raise RuntimeError('Could not determine ID for asset, cannot continue!') responses = self.getValueForLinkType(value='ADDRESS',linkType=linkType, id=entityID,type='asset', xmlDOM=xmlDOM) if not len(responses) > 0: self.logger('No responses returned for asset!','error') return '' return responses[0] ''' ## If we weren't passed our DOM, load it if not xmlDOM: xmlDOM = self.getParentLinksXML(id=id) ## If we still have no DOM, exit if not xmlDOM: self.logger('Could not retrieve FCSPath, no xmlDOM loaded!','error') return False assetDict = {} linkAddress = '' for values in xmlDOM.getElementsByTagName('values'): loopDied = False for value in values.getElementsByTagName('value'): valueID = value.attributes['id'].value ##self.logger("VALUE ID ATTRIB:%s" % valueID,'debug') if valueID == 'LINK_TYPE': ## Check to see if it's the thumbnail (LINK_TYPE of 5) assetDict['LINK_TYPE'] = int(value.childNodes[1].childNodes[0].data) if not assetDict['LINK_TYPE'] == linkType: loopDied = True break if valueID == 'ADDRESS': assetDict['ADDRESS'] = value.childNodes[1].childNodes[0].data if valueID == 'DESC_DEVICE': assetDict['DESC_DEVICE'] = value.childNodes[1].childNodes[0].data if valueID == 'DESC_DEVICE_ADDRESS': assetDict['DESC_DEVICE_ADDRESS'] = value.childNodes[1].childNodes[0].data if not loopDied and 'LINK_TYPE' in assetDict and assetDict['LINK_TYPE'] == linkType: self.logger('link type:%s Address: %s ' % (assetDict['LINK_TYPE'],assetDict['ADDRESS']),'debug') break if not 'ADDRESS' or not 'ADDRESS' in assetDict or 'ADDRESS' in assetDict and not assetDict['ADDRESS']: self.logger('Could not find LINK_TYPE:%s in XML object!' % linkType,'error') return False return assetDict['ADDRESS'] '''
[docs] def getFSPathForLinkType(self,linkType,id='',xmlDOM=''): '''Returns a filesystem path from a linked asset''' ## todo: document xmlDOM functionality. if not id: if self.entityID and self.entityType == 'asset': assetID = self.entityID else: self.logger('Asset ID was not provided and is not stored, cannot continue!','error') raise RuntimeError('Asset ID was not provided and is not stored, cannot continue!') else: assetID = id if not xmlDOM: xmlDOM = self.getParentLinksXML(id=assetID) if not xmlDOM: self.logger('Could not retrieve FCPath, no xmlDOM loaded!','error') return False ## Get our FCS Address for the link assetAddress = self.getFCSPathForLinkType(linkType,id=assetID,xmlDOM=xmlDOM) if not assetAddress: return False ## Convert the FCS Path to an FS Path self.logger('Converting address: %s to known FileSystem address!' % assetAddress) assetPath = self.getFSPathFromFCSPath(assetAddress) if assetPath: self.logger('Using address: %s' % assetPath,'debug') return assetPath else: self.logger('Could not convert address: %s' % assetAddress,'error') return False
[docs] def getChildLinksXML(self,id='',type=''): '''Gets an xml.dom.minidom object of child links data from fcsvr_client sister object to getParentLinksXML.''' ## Determine our entityType entityType = '' if not type and self.entityType: entityType = self.entityType elif not self.entityType: entityType = 'asset' elif type: entityType = type ## Determine our id entityID = '' if not id and self.entityType == entityType: entityID = self.entityID elif not id and not self.entityType == entityType: self.logger('Requested entity type does not match stored values!') raise RuntimeError('Requested entity type does not match stored values!') elif id: entityID = id if not entityID or not entityType: self.logger('Passed invalid entity data, type or id missing!') raise RuntimeError('Passed invalid entity data, type or id missing!') ## If our assetID lookup matches our entityID and we have a cached value, return it if entityID == self.entityID and type == self.entityType and self.childXMLObject: return self.childXMLObject ## Fetch our data from FCSVRXML fcsvrCMDString = "'%s' list_child_links /%s/%s --xml" % (self.pathToFCSVRClient, entityType, entityID) fcsvrCMD = subprocess.Popen(fcsvrCMDString,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvr_client command:%s' % fcsvrCMDString,'debug') ##self.logger('fcsvr_client output: %s tempfilepath: %s' % (fcsvrCMD_STDOUT,tempFilePath),'debug') if not fcsvrCMD.returncode == 0: ##self.logger('fcsvr_client reported an error: %s%s' % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR),'error') ##raise RuntimeError('fcsvr_client reported an error: %s' % (fcsvrCMD_STDOUT,fcsvrCMD_STDERR)) return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDString) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client','error') return False if entityID == self.entityID: self.parentXMLObject = myDom return myDom
[docs] def getParentLinksXML(self,id='',type=''): '''Returns an xml.dom.minidom object of parent links data returned from fcsvr_client''' ## Determine our entityType entityType = '' if not type and self.entityType: entityType = self.entityType elif not self.entityType: entityType = 'asset' elif type: entityType = type ## Determine our id entityID = '' if not id and self.entityType == entityType: entityID = self.entityID elif not id and not self.entityType == entityType: self.logger('Requested entity type does not match stored values!') raise RuntimeError('Requested entity type does not match stored values!') elif id: entityID = id ## If our assetID lookup matches our entityID and we have a cached value, return it if entityID == self.entityID and self.parentXMLObject: return self.parentXMLObject ## Fetch our data from FCSVRXML fcsvrCMDString = '"%s" list_parent_links /%s/%s --xml' % (self.pathToFCSVRClient, entityType, entityID) fcsvrCMD = subprocess.Popen(fcsvrCMDString,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvr_client command:%s' % fcsvrCMDString,'debug') ##self.logger('fcsvr_client output: %s tempfilepath: %s' % (fcsvrCMD_STDOUT,tempFilePath),'debug') if not fcsvrCMD.returncode == 0: ##self.logger('fcsvr_client reported an error: %s' % fcsvrCMD_STDERR,'error') ##raise RuntimeError('fcsvr_client reported an error: %s' % fcsvrCMD_STDERR) return self.fcsvr_client_error(errorString=fcsvrCMD_STDOUT, cmdString=fcsvrCMDString) ## Create a dom object from our string: try: myDom = minidom.parseString(fcsvrCMD_STDOUT) except: self.logger('Could not parse output from fcsvr_client','error') return False if entityID == self.entityID: self.parentXMLObject = myDom return myDom
[docs] def getThumbnailPath(self,assetID='',xmlDOM=''): '''DEPRICATED: use getFilePathForThumbnail''' return self.getFilePathForThumbnail(id=assetID)
[docs] def flushCaches(self): '''Flushes caches''' self.parentXMLObject = '' self.childXMLObject = '' return True
[docs] def fcsvr_client_create(self,address='',entityType='',parentAddress='',parentLinkType=''): '''This is a wrapper around fcsvr_client, if there are fields set in self.fields, it will import with generate XML to a temp file and pass it during creation''' ## currently a place holder return False
[docs] def fcsvr_client_createasset(self,fcsPath,mdSet='pa_asset_media', backgroundAnalyze=False,projAddress='',setMD=True): '''This is the createasset wrapper around fcsvr_client. If there are fields set in self.fields, It will output XML to a temp file and then call fcsvr_client to create the entity. If self.entityType is 'asset', then we load from fcsvr_client and return our self, if our entityType is not asset, we clone ourself and return a new FCSVRClient object with entityType 'asset' WARNING: fcsPath should be a quoted path, as returned by quoteFSPath() failure to comply with this CAN result in a database crash, or possible corruption if fcsvr_client is passed unquoted special characters!!! ''' if not self.entityType == 'asset': self.logger('Object entityType is %s, not %s, cloning object!' % entityType,'warning') obj = copy.copy(self) else: obj = self if not obj.entityType in obj.registeredEntities: self.logger('Could not create entity, entityType:% is not valid!' % entityType,'error') return False cmdArgs = ' createasset ' if backgroundAnalyze: cmdArgs += '--background ' if projAddress: if projAddress[0:9] == '/project/': cmdArgs += '--projaddr %s' % projAddress else: self.logger('Found invalid project address: %s, cannot link!' % projAddress,'warning') cmdArgs += ' %s %s' % (mdSet,fcsPath) ## If we can escalate to root, do so if self.useSudo and not os.geteuid() == 0: fcsvrCMDTXT = '/usr/bin/sudo \'%s\' %s' % (self.pathToFCSVRClient,cmdArgs) else: if not os.geteuid() == 0: self.logger('Could not create asset, permission denied!','error') raise FCSVRClientPermissionDenied('createasset',cmdArgs) else: self.useSudo = False fcsvrCMDTXT = '\'%s\' %s' % (self.pathToFCSVRClient,cmdArgs) ## Run our fcsvr_client command. fcsvrCMD = subprocess.Popen('%s' % fcsvrCMDTXT,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) fcsvrCMD_STDOUT,fcsvrCMD_STDERR = fcsvrCMD.communicate() self.logger('fcsvrCMD:\n %s' % "'%s' %s" % (obj.pathToFCSVRClient,cmdArgs),'debug') self.logger('fcsvr_client output: %s fcsPath: %s' % (fcsvrCMD_STDOUT,fcsPath),'debug') if not fcsvrCMD.returncode == 0: ##self.logger('FCSVRClient Error: %s\n%s\nCommand Syntax: %s' ##% (fcsvrCMD_STDOUT,fcsvrCMD_STDERR,fcsvrCMDTXT),'error') ##return False if fcsvrCMD_STDOUT: reportString = fcsvrCMD_STDOUT else: reportString = fcsvrCMD_STDERR return self.fcsvr_client_error(errorString=reportString, cmdString=fcsvrCMDTXT) ## Resolve our entity ID from STDOUT try: match = re.match('/asset/(\d+)',fcsvrCMD_STDOUT) assetID = match.group(1) except: self.logger('Could not retrieve asset id from string: %s' % fcsvrCMD_STDOUT,'error') return False self.logger('Created new Asset with ID:%s' % assetID,'debug') obj.entityType = 'asset' obj.entityID = assetID if obj.fields and setMD: obj.setMD() return obj
[docs] def fcsvr_client_error(self,errorString='',cmdString=''): '''This method interprets an fcsvr_client error string and will throw the appropriate exception, it should be called if fcsvr_client returns a non-0 exit code.''' self.logger('fcsvr_client_error() Hit!','debug') resolvedCode = '' resolvedDesc = '' resolvedNode = '' resolvedSourceFile = '' resolvedSourceLine = '' ## Create a dom object from our string: try: myDom = minidom.parseString(errorString) sessionRoot = myDom.childNodes[0] for value in sessionRoot.getElementsByTagName('value'): #self.logger('dbFieldNameForFieldName() rootValue nodename: %s' % rootValue.nodeName,'debug') valueID = value.attributes['id'].value self.logger('fcsvr_client_error() Searching value: %s' % valueID,'debug') if valueID == 'CODE': resolvedCode = value.childNodes[1].childNodes[0].data self.logger('fcsvr_client_error() found error code:%s' % resolvedCode,'debug') elif valueID == 'DESC': resolvedDesc = value.childNodes[1].childNodes[0].data elif valueID == 'NODE': resolvedNode = value.childNodes[1].childNodes[0].data elif valueID == 'SRC_FILE': resolvedSourceFile = value.childNodes[1].childNodes[0].data elif valueID == 'SRC_LINE': resolvedSourceLine = value.childNodes[1].childNodes[0].data except: ## If the DOM failed, attempt to extract from string (some fcsvr_client ## actions do not support -xml try: stringSearchResults = re.search('{.*CODE = (.*), DESC = (.*)' ', NODE = (.*), SRC_FILE = (.*), SRC_LINE = (.*) }',errorString) resolvedCode = stringSearchResults.groups()[0] resolvedDesc = stringSearchResults.groups()[1] resolvedNode = stringSearchResults.groups()[2] resolvedSourceFile = stringSearchResults.groups()[3] resolvedSourceLine = stringSearchResults.groups()[4] except: raise FCSVRClientError(errorString='An Unknown error occurred, could ' 'not parse fcsvr_client output: %s' % errorString,cmdString=cmdString) #try: if resolvedCode == 'E_COM': if resolvedDesc: msg = resolvedDesc else: msg = ('Server is not running!') raise FCSVROfflineError(errorString=msg) elif resolvedCode == 'E_FILE': if resolvedDesc: msg = resolvedDesc else: msg = ('No such file or directory!') raise FCSVRClientFileError(msg) elif resolvedCode == 'E_DUPLICATE': if resolvedDesc: msg = resolvedDesc else: msg = ('A duplicate action has been recorded: this action may have ' 'already been applied!') raise FCSDuplicateError(msg) elif resolvedCode == 'E_NOTSUPP': if resolvedDesc: msg = resolvedDesc else: msg = ('Action is not supported!!') raise FCSVRClientError(msg) else: raise FCSVRClientError(errorString='An Unknown error occurred. Code: \'%s\'' ' Desc: \'%s\' ErrorString:\'%s\'' % (resolvedCode,resolvedDesc,errorString) ,cmdString=cmdString) return False #### BEGIN EXCEPTIONS ####
[docs]class FCSFieldNotFoundError(Exception): def __init__(self, fieldName='',dbname=''): self.fieldName = fieldName self.dbname = dbname def __str__(self): if self.dbname: returnString = 'No Field with dbkey:\'%s\' exists!' % self.dbname return repr(returnString) else: returnString = 'No Field with key:\'%s\' exists!' % self.fieldName return repr(returnString)
[docs]class FCSAssetOfflineError(Exception): def __init__(self,assetID='',assetPath='',assetTitle=''): self.assetID = assetID self.assetPath = assetPath self.assetTitle = assetTitle def __str__(self): returnString = 'Asset is offline.' if self.assetID: returnString += ' AssetID:%s' % self.assetID if self.assetPath: returnString += ' AssetPath:\'%s\'' % self.assetID if self.assetTitle: returnString += ' Title:\'%s\'' % self.assetTitle return repr(returnString)
[docs]class FCSEntityNotFoundError(Exception): def __init__(self,entityType='asset',entityID='',entityPath='',entityTitle=''): self.entityType = entityType self.entityID = entityID self.entityPath = entityPath self.entityTitle = entityTitle def __str__(self): if self.entityID: return repr('No %s with id:\'%s\' exists!' % (self.entityType,self.entityID)) elif self.entityPath: return repr('No %s with path:\'%s\' exists!' % (self.entityType,self.entityPath)) elif self.entityTitle: return repr('No %s with Title:\'%s\' exists!' % (self.entityType,self.entityTitle)) else: return repr('No %s with id:\'%s\' exists!' % (self.entityType,self.entityID))
[docs]class FCSValidationError(Exception): def __init__(self, fieldName='', dataType='', value='', validationInfo=''): self.fieldName = fieldName self.dataType = dataType self.value = value self.validationInfo = validationInfo def __str__(self): string = ('Could not set new value for field:\'%s\', dataType:\'%s\', ' 'value:\'%s\' did not pass validation! %s' % (self.fieldName,self.dataType,self.value,self.validationInfo)) return repr(string)
[docs]class FCSDuplicateError(Exception): def __init__(self, error=''): self.error = error def __str__(self): return repr(self.error)
[docs]class FCSError(Exception): def __init__(self, errorString): self.errorString = errorString def __str__(self): return repr('FCS Error: %s\n' % errorString)
[docs]class FCSVRClientError(Exception): def __init__(self,errorString,cmdString=''): self.errorString = errorString self.cmdString = cmdString def __str__(self): return repr('fcsvr_client Error: %s\nCommand Syntax:%s\n' % (self.errorString,self.cmdString))
[docs]class FCSVRClientFileError(Exception): def __init__(self,errorString,cmdString=''): self.errorString = errorString self.cmdString = cmdString def __str__(self): message = ('fcsvr_client Error: %s\nCommand Syntax:%s\n' % (self.errorString,self.cmdString)) return repr(message)
[docs]class FCSVROfflineError(Exception): def __init__(self,errorString): self.errorString = errorString def __str__(self): return repr(self.errorString)
[docs]class FCSVRClientPermissionDenied(Exception): def __init__(self,action,cmdString=''): self.action = action self.cmdString = cmdString def __str__(self): return repr('fcsvr_client Permission denied for action: %s cmdString: %s' % (self.action,self.cmdString))
[docs]class FCSObjectLoadError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value)
[docs]class FCSProductionLoadError(Exception): def __init__(self,errorString,retString,cmdString): self.errorString = errorString self.retString = retString self.cmdString = cmdString def __str__(self): retString = self.errorString if self.cmdString: retString += '\nCommand: %s' % (self.cmdString) if self.retString: retString += '\nReturned Text: %s' % self.retString return retString