# -*- coding: utf-8 -*-

def _hashValuesNoneToEmptyString(self, hash):
	for (key, value) in hash.items():
		if value is None:
			hash[key] = ''
	return hash

def exit(self):
	self.backend_exit()

def deleteOpsiBase(self):
	self.backend_deleteBase()

def createOpsiBase(self):
	self.backend_createBase()

def writeLog(self, type, data, objectId=None, append=True):
	self.log_write(logType = type, data = data, objectId = objectId, append = append)

def readLog(self, type, objectId=None, maxSize=0):
	return self.log_read(logType = type, objectId = objectId, maxSize = maxSize)

def getOpsiInformation_hash(self):
	return self.backend_info()

def getBackendInfos_listOfHashes(self):
	return [{config[0]: config[1]} for config in self.dispatcher_getConfig()]

def getDomain(self):
	from OPSI.Backend.Backend import OPSI_GLOBAL_CONF
	return forceUnicodeLower('.'.join(getfqdn(conf=OPSI_GLOBAL_CONF).split('.')[1:]))

def getPossibleMethods_listOfHashes(self):
	possibleMethods = []
	for method in self.backend_getInterface():
		compatible = True
		for param in method["params"]:
			if param.startswith('**'):
				compatible = False
				break
		if compatible:
			possibleMethods.append({"name": method["name"], "params": method["params"]})
	return possibleMethods

def authenticated(self):
	if self.accessControl_authenticated():
		return True
	raise Exception(u"Not authenticated")

def getGeneralConfig_hash(self, objectId=None):
	if objectId:
		objectId = forceFqdn(objectId)
		if objectId in self.host_getIdents(type='OpsiDepotserver', returnType='unicode'):
			objectId = None

	result = {}
	if objectId:
		addConfigStateDefaults = self._backend.backend_getOptions().get('addConfigStateDefaults', False)
		try:
			self._backend.backend_setOptions({'addConfigStateDefaults': True})
			for configState in self.configState_getObjects(objectId=objectId):
				result[configState.configId] = u','.join(forceUnicodeList(configState.getValues()))
		finally:
			self._backend.backend_setOptions({'addConfigStateDefaults': addConfigStateDefaults})
	else:
		for config in self.config_getObjects():
			result[config.id] = u','.join(forceUnicodeList(config.defaultValues))
	return result

def getGeneralConfigValue(self, key, objectId=None):
	return self.getGeneralConfig_hash(objectId=objectId).get(key)

def setGeneralConfig(self, config, objectId=None):
	if objectId:
		objectId = forceFqdn(objectId)
		if objectId in self.host_getIdents(type='OpsiDepotserver', returnType='unicode'):
			objectId = None
	else:
		objectId = None

	knownConfigIds = frozenset(self.config_getIdents(returnType='unicode'))
	for configId in knownConfigIds:
		if configId not in config:
			self.config_delete(id=configId)

	boolvalues = frozenset(['yes', 'no', 'on', 'off', '1', '0', 'true', 'false'])

	def getNewConfigs():
		hasNoObjectId = bool(objectId is None)
		for configId, value in config.iteritems():
			if hasNoObjectId or configId not in knownConfigIds:
				if value.lower() in boolvalues:
					value = forceBool(value)
					yield BoolConfig(id=configId, defaultValues=[value])
				else:
					yield UnicodeConfig(
						id=configId,
						defaultValues=[value],
						possibleValues=[value],
						editable=True,
						multiValue=False
					)

	def getNewConfigStates():
		if objectId is not None:
			for configId, value in config.iteritems():
				if value.lower() in boolvalues:
					value = forceBool(value)

				yield ConfigState(
					configId=configId,
					objectId=objectId,
					values=[value]
				)

	self.config_createObjects(getNewConfigs())
	self.configState_createObjects(getNewConfigStates())

def setGeneralConfigValue(self, key, value, objectId=None):
	generalConfig = self.getGeneralConfig_hash(objectId=objectId)
	generalConfig[key] = value
	return self.setGeneralConfig(generalConfig, objectId=objectId)

def deleteGeneralConfig(self, objectId):
	return self.configState_delete(configId=[], objectId=forceObjectId(objectId))

def setNetworkConfig(self, config, objectId=None):
	if objectId and 'depotId' in config.keys():
		return self.setGeneralConfigValue('clientconfig.depot.id', config['depotId'], objectId)
	raise NotImplementedError("Please use general config to change values")

def setNetworkConfigValue(self, key, value, objectId=None):
	raise NotImplementedError("Please use general config to change values")

def getNetworkConfig_hash(self, objectId=None):
	configServerIdents = self.host_getIdents(type='OpsiConfigserver', returnType='unicode')
	if not configServerIdents:
		raise BackendMissingDataError(u"No configserver found")

	depotId = self.getGeneralConfigValue('clientconfig.depot.id', objectId=objectId)

	depots = self.host_getObjects(type='OpsiDepotserver', id=depotId)
	if not depots:
		raise BackendMissingDataError(u"Depotserver '%s' not found" % depotId)

	depotDrive = self.getGeneralConfigValue('clientconfig.depot.drive', objectId=objectId)
	depotUrl = depots[0].getDepotRemoteUrl()

	return {
		'opsiServer': configServerIdents[0],
		'nextBootServiceURL': self.getGeneralConfigValue('clientconfig.configserver.url', objectId=objectId),
		'nextBootServerType': u'service',
		'depotId': depotId,
		'depotUrl': depotUrl,
		'depotDrive': depotDrive,
		'configUrl': depotUrl.replace('/install', '/pcpatch'),
		'configDrive': depotDrive,
		'utilsUrl': depotUrl.replace('/install', '/utils'),
		'utilsDrive': depotDrive,
		'winDomain': self.getGeneralConfigValue('clientconfig.windows.domain', objectId=objectId),
	}

def getNetworkConfigValue(self, key, objectId=None):
	return self.getNetworkConfig_hash(objectId).get(key)

def deleteNetworkConfig(self, objectId):
	raise NotImplementedError("Please use general config to change values")

def getGroupIds_list(self):
	return self.group_getIdents(returnType='unicode')

def getHostGroupTree_hash(self):
	groups = {}
	childs = {}
	for group in self.group_getObjects(attributes=['id', 'parentGroupId']):
		if group.getParentGroupId():
			if not childs.has_key(group.getParentGroupId()):
				childs[group.getParentGroupId()] = {}
			childs[group.getParentGroupId()][group.getId()] = {}
		else:
			groups[group.getId()] = {}

	def insertGroup(gid, g, gs):
		if gid in gs.keys():
			gs[gid] = g
			return True
		for ng in gs.keys():
			if insertGroup(gid, g, gs[ng]):
				return True
		return False

	while childs.keys():
		left = len(childs.keys())
		for groupId in childs.keys():
			if insertGroup(groupId, childs[groupId], groups):
				del childs[groupId]
		if (left == len(childs.keys())):
			raise BackendIOError("Error in host groups")
	return groups

def createGroup(self, groupId, members=[], description="", parentGroupId=""):
	if not members:
		members = []
	if not parentGroupId:
		parentGroupId = None
	self.group_createHostGroup(id=groupId, description=description, notes=u'', parentGroupId=parentGroupId)
	objects = [ObjectToGroup(groupType='HostGroup', groupId=groupId, objectId=member) for member in members]
	self.objectToGroup_createObjects(objects)

def deleteGroup(self, groupId):
	self.group_delete(id=groupId)

def getHostId(self, hostname=None):
	if not hostname:
		raise NotImplementedError(u"Hostname not given")
	return '%s.%s' % (forceUnicode(hostname), self.getDomain())

def setPXEBootConfiguration(self, hostId, args={}):
	raise NotImplementedError(u"Not Implemented")

def unsetPXEBootConfiguration(self, hostId):
	raise NotImplementedError(u"Not Implemented")

def powerOnHost(self, hostId):
	return self.hostControl_start(hostId)

def getIpAddress(self, hostId):
	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(attributes=['ipAddress'], id=hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host '%s' not found" % hostId)
	return hosts[0].getIpAddress() or u''

def createServer(self, serverName, domain, description=None, notes=None):
	id = forceHostId(u'.'.join((forceHostname(serverName), forceDomain(domain))))
	self.host_createOpsiConfigserver(id=id, description=description, notes=notes)
	return id

def createClient(self, clientName, domain, description=None, notes=None, ipAddress=None, hardwareAddress=None):
	if not ipAddress:
		ipAddress = None
	if not hardwareAddress:
		hardwareAddress = None

	id = forceHostId(u'.'.join((forceHostname(clientName), forceDomain(domain))))
	self.host_createOpsiClient(id=id, description=description, notes=notes, ipAddress=ipAddress, hardwareAddress=hardwareAddress)
	return id

def deleteServer(self, serverId):
	self.host_delete(id=forceHostId(serverId))

def deleteClient(self, clientId):
	self.host_delete(id=forceHostId(clientId))

def setHostLastSeen(self, hostId, timestamp):
	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(id=hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host '%s' not found" % hostId)
	hosts[0].setLastSeen(timestamp)
	self.host_updateObject(hosts[0])

def setHostDescription(self, hostId, description):
	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(id=hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host '%s' not found" % hostId)
	hosts[0].setDescription(description)
	self.host_updateObject(hosts[0])

def setHostNotes(self, hostId, notes):
	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(id=hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host '%s' not found" % hostId)
	hosts[0].setNotes(notes)
	self.host_updateObject(hosts[0])

def getSoftwareInformation_hash(self, hostId):
	import time

	auditSoftwares = {}
	for aus in self.auditSoftware_getObjects():
		try:
			auditSoftwares[aus.name][aus.version][aus.subVersion][aus.language][aus.architecture] = aus
		except KeyError:
			try:
				auditSoftwares[aus.name][aus.version][aus.subVersion][aus.language] = {aus.architecture: aus}
			except KeyError:
				try:
					auditSoftwares[aus.name][aus.version][aus.subVersion] = {aus.language: {aus.architecture: aus}}
				except KeyError:
					try:
						auditSoftwares[aus.name][aus.version] = {aus.subVersion: {aus.language: {aus.architecture: aus}}}
					except KeyError:
						auditSoftwares[aus.name] = {aus.version: {aus.subVersion: {aus.language: {aus.architecture: aus}}}}


	keysToRemove = (
		"clientId",  "name",  "version",  "subVersion",  "language",
		"architecture",  "state",  "firstseen",  "lastseen"
	)
	result = {}
	scantime = time.strptime("2000-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")

	for auditSoftwareOnClient in self.auditSoftwareOnClient_getObjects(clientId=hostId):
		lastseen = time.strptime(auditSoftwareOnClient.getLastseen(), "%Y-%m-%d %H:%M:%S")
		if scantime < lastseen:
			scantime = lastseen

		try:
			aus = (auditSoftwares
				[auditSoftwareOnClient.name]
				[auditSoftwareOnClient.version]
				[auditSoftwareOnClient.subVersion]
				[auditSoftwareOnClient.language]
				[auditSoftwareOnClient.architecture]
			)
		except KeyError:
			logger.error(
				u'No auditSoftwares found with filter: {"name": {0}, '
				u'"version": {1}, "subVersion": {2}, "language": {3}, '
				u'"architecture": {4}}'.format(
					auditSoftwareOnClient.getName(),
					auditSoftwareOnClient.getVersion(),
					auditSoftwareOnClient.getSubVersion(),
					auditSoftwareOnClient.getLanguage(),
					auditSoftwareOnClient.getArchitecture()
				)
			)
			continue

		hash = auditSoftwareOnClient.toHash()
		hash["softwareId"] = aus.getWindowsSoftwareId()
		hash["displayName"] = aus.getWindowsDisplayName()
		hash["displayVersion"] = aus.getWindowsDisplayVersion()
		hash["installSize"] = aus.getInstallSize()

		for keyToRemove in keysToRemove:
			del hash[keyToRemove]

		if aus.getWindowsSoftwareId():
			result[aus.getWindowsSoftwareId()] = self._hashValuesNoneToEmptyString(hash)

	result['SCANPROPERTIES'] = {'scantime': time.strftime("%Y-%m-%d %H:%M:%S", scantime)}
	return result

def getSoftwareInformation_listOfHashes(self):
	result = []
	for auditSoftware in self.auditSoftware_getObjects():
		hash = auditSoftware.toHash()
		hash['displayName'] = hash['windowsDisplayName']
		del hash['windowsDisplayName']
		hash['displayVersion'] = hash['windowsDisplayVersion']
		del hash['windowsDisplayVersion']
		del hash['type']
		del hash['name']
		del hash['architecture']
		del hash['language']
		del hash['version']
		del hash['subVersion']
		if not hash['installSize']:
			hash['installSize'] = 0
		hash['uninstallString'] = u''
		hash['binaryName'] = u''
		hash['installedOn'] = []
		for auditSoftwareOnClient in self.auditSoftwareOnClient_getObjects(
							name=auditSoftware.getName(),
							version=auditSoftware.getVersion(),
							subVersion=auditSoftware.getSubVersion(),
							language=auditSoftware.getLanguage(),
							architecture=auditSoftware.getArchitecture()):
			if auditSoftwareOnClient.getUninstallString():
				hash['uninstallString'] = auditSoftwareOnClient.getUninstallString()
			if auditSoftwareOnClient.getBinaryName():
				hash['binaryName'] = auditSoftwareOnClient.getBinaryName()
			hash['installedOn'].append(auditSoftwareOnClient.getClientId())
		hash['installationCount'] = len(hash['installedOn'])
		result.append(hash)
	return result

def setSoftwareInformation(self, hostId, info):
	hostId = forceHostId(hostId)
	self.auditSoftwareOnClient_delete(name=[], version=[], subVersion=[], language=[], architecture=[], clientId=hostId)

	for (windowsSoftwareId, value) in info.items():
		if (windowsSoftwareId == 'SCANPROPERTIES'):
			continue

		auditSoftwareHash = {
			'name': u'',
			'version': u'',
			'subVersion': u'',
			'language': u'',
			'architecture': u'x86',
			'windowsSoftwareId': windowsSoftwareId,
			'windowsDisplayName': u'',
			'windowsDisplayVersion': u'',
			'installSize': -1,
		}
		auditSoftwareOnClientHash = {
			'clientId': hostId,
			'uninstallString': u'',
			'binaryName': u'',
			'usageFrequency': -1,
			'lastUsed': None,
		}
		for (k, v) in value.items():
			if k.lower() == 'displayname':
				auditSoftwareHash['name'] = auditSoftwareHash['windowsDisplayName'] = v
			elif k.lower() == 'displayversion':
				auditSoftwareHash['version'] = auditSoftwareHash['windowsDisplayVersion'] = v
			elif k.lower() == 'installsize':
				auditSoftwareHash['installSize'] = v
			else:
				for kk in auditSoftwareOnClientHash.keys():
					if k.lower() == kk.lower():
						auditSoftwareOnClientHash[k] = v
						break

		if not auditSoftwareHash['name']:
			auditSoftwareHash['name'] = auditSoftwareHash['windowsSoftwareId']
		auditSoftwareOnClientHash['name'] = auditSoftwareHash['name']
		auditSoftwareOnClientHash['version'] = auditSoftwareHash['version']
		auditSoftwareOnClientHash['subVersion'] = auditSoftwareHash['subVersion']
		auditSoftwareOnClientHash['language'] = auditSoftwareHash['language']
		auditSoftwareOnClientHash['architecture'] = auditSoftwareHash['architecture']

		self.auditSoftware_createObjects( AuditSoftware.fromHash(auditSoftwareHash) )
		self.auditSoftwareOnClient_createObjects( AuditSoftwareOnClient.fromHash(auditSoftwareOnClientHash) )

def deleteSoftwareInformation(self, hostId):
	hostId = forceHostId(hostId)
	self.auditSoftwareOnClient_delete(clientId = hostId)

def softwareInformationToProductInstallationStatus(self, hostIds=[]):
	raise NotImplementedError("Not Implemented")
	'''
	This method sets product installation states based on software inventory results.
	'''
	if not isinstance(hostIds, (list, tuple)):
		hostIds = [hostIds]
	if not hostIds:
		hostIds = self.getClientIds_list()
	windowsSoftwareIds = {}

	for product in self.getProducts_listOfHashes():
		if product.get("windowsSoftwareIds", []):
			for swid in product["windowsSoftwareIds"]:
				windowsSoftwareIds[swid] = product['productId']

	errors = []
	for hostId in hostIds:
		try:
			sw = self.getSoftwareInformation_hash(hostId)
			for (softwareId, info) in sw.items():
				if windowsSoftwareIds.has_key(softwareId):
					status = self.getProductInstallationStatus_hash(windowsSoftwareIds[softwareId], hostId)
					if (status.get("installationStatus") == "installed"):
						continue
					self.setProductState(windowsSoftwareIds[softwareId], hostId, installationStatus="installed", packageVersion="0")
		except Exception, e:
			logger.error("softwareInformationToProductInstallationStatus error: %s" % e)
			errors.append(e)

	if errors:
		raise Exception("Error occurred: %s" % errors)

def getOpsiHWAuditConf(self, locale=None):
	return self.auditHardware_getConfig(locale)

def getHardwareInformation_hash(self, hostId):
	import time

	hostId = forceHostId(hostId)
	info = {}
	scantime = time.strptime("2000-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
	for auditHardwareOnHost in self.auditHardwareOnHost_getObjects(hostId=hostId, state=1):
		hardwareClass = auditHardwareOnHost.getHardwareClass()
		if hardwareClass not in info:
			info[hardwareClass] = []
		data = auditHardwareOnHost.toHash()
		lastseen = time.strptime(str(data['lastseen']), "%Y-%m-%d %H:%M:%S")
		if scantime < lastseen:
			scantime = lastseen
		for key in ('hardwareClass', 'hostId', 'firstseen', 'state', 'lastseen'):
			del data[key]
		info[hardwareClass].append(data)

	info['SCANPROPERTIES'] = [ {'scantime': time.strftime("%Y-%m-%d %H:%M:%S", scantime) } ]
	return info

def getHardwareInformation_listOfHashes(self, hostId):
	return []

def setHardwareInformation(self, hostId, info):
	hostId = forceHostId(hostId)
	self.auditHardwareOnHost_setObsolete(hostId)
	auditHardwareOnHosts = []
	for (hardwareClass, devices) in info.items():
		if (hardwareClass == 'SCANPROPERTIES'):
			continue

		for device in devices:
			data = {'hardwareClass': hardwareClass}
			for (attribute, value) in device.items():
				data[str(attribute)] = value
			data['hostId'] = hostId
			auditHardwareOnHosts.append(AuditHardwareOnHost.fromHash(data))
	self.auditHardwareOnHost_updateObjects(auditHardwareOnHosts)

def deleteHardwareInformation(self, hostId):
	hostId = forceHostId(hostId)
	self.auditHardwareOnHost_delete(hostId = hostId, hardwareClass = [])

def filterHostsByHardwareInformation(self, hostIds, hwFilter):
	raise NotImplementedError("Not Implemented")

def getHost_hash(self, hostId):
	import re

	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(id = hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host '%s' not found" % hostId)
	hash = hosts[0].toHash()
	hash['hostId'] = hash['id']
	match = re.search('^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$', hash.get('created', ''))
	if match:
		hash['created'] = u'%s%s%s%s%s%s' % ( match.group(1), match.group(2), match.group(3), match.group(4), match.group(5), match.group(6) )
	match = re.search('^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$', hash.get('lastSeen', ''))
	if match:
		hash['lastSeen'] = u'%s%s%s%s%s%s' % ( match.group(1), match.group(2), match.group(3), match.group(4), match.group(5), match.group(6) )
	del hash['type']
	del hash['id']
	return self._hashValuesNoneToEmptyString(hash)

def getClients_listOfHashes(self, serverId=None, depotIds=[], groupId=None, productId=None, installationStatus=None, actionRequest=None, productVersion=None, packageVersion=None, hwFilter=None):
	if not serverId:
		serverId = []
	if not depotIds:
		depotIds = []
	if not groupId:
		groupId = []
	if not productId:
		productId = []
	if not installationStatus:
		installationStatus = []
	if not actionRequest:
		actionRequest = []
	if not productVersion:
		productVersion = []
	if not packageVersion:
		packageVersion = []
	if not hwFilter:
		hwFilter = []

	clientToDepotservers = self.configState_getClientToDepotserver(depotIds=forceHostIdList(depotIds))
	if depotIds:
		clientIds = [clientToDepotserver['clientId'] for clientToDepotserver in clientToDepotservers]

		if not clientIds:
			return []
	else:
		clientIds = []

	if groupId:
		filteredClientIds = [ident['objectId'] for ident
							 in self.objectToGroup_getIdents(groupType='HostGroup', objectId=clientIds, groupId=groupId, returnType='dict')]

		if not filteredClientIds:
			return []

		clientIds = filteredClientIds

	if productId or productVersion or packageVersion or installationStatus or actionRequest:
		clientIds = self.backend_searchIdents(u'(&(&(objectClass=OpsiClient)(id=%s))(&(objectClass=ProductOnClient)(productId=%s)(productVersion=%s)(packageVersion=%s)(installationStatus=%s)(actionRequest=%s)))' % \
			(clientIds, productId, productVersion, packageVersion, installationStatus, actionRequest))

		if not clientIds:
			return []

	if hwFilter:
		filter = [u'(&(&(objectClass=OpsiClient)(id=%s))' % clientIds, ]
		for (k, v) in hwFilter.items():
			(hwClass, attr) = k.split('.')
			match = re.search('^([<=>]=?)(\d+.*)$', v)
			if match:
				operator = match.group(1)
				v = match.group(2)
				if operator == '==':
					operator = '='
				elif operator == '<':
					operator = '<='

					try:
						v = forceInt(v) + 1
					except Exception:
						pass

				elif operator == '>':
					operator = '>='
					try:
						v = forceInt(v) - 1
					except Exception:
						pass

				v = u'%s%s' % (operator, v)
			else:
				v = u'=%s' % v

			filter.append(u'(&(objectClass=AuditHardwareOnHost)(hardwareClass=%s)(%s%s))' % (hwClass, attr, v))
		filter.append(u')')

		filter = ''.join(filter)

		clientIds = self.backend_searchIdents(filter)
		if not clientIds:
			return []

	result = []
	for client in self.host_getObjects(type='OpsiClient', id=clientIds):
		hash = client.toHash()
		hash['hostId'] = hash['id']

		if hash.get('created') is None:
			hash['created'] = u''
		else:
			match = re.search('^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$', hash.get('created', ''))
			if match:
				hash['created'] = u'%s%s%s%s%s%s' % ( match.group(1), match.group(2), match.group(3), match.group(4), match.group(5), match.group(6) )

		if hash.get('lastSeen') is None:
			hash['lastSeen'] = u''
		else:
			match = re.search('^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$', hash.get('lastSeen', ''))
			if match:
				hash['lastSeen'] = u'%s%s%s%s%s%s' % ( match.group(1), match.group(2), match.group(3), match.group(4), match.group(5), match.group(6) )

		del hash['type']
		del hash['id']

		hash['depotId'] = u''
		for clientToDepotserver in clientToDepotservers:
			if client.getId() == clientToDepotserver['clientId']:
				hash['depotId'] = clientToDepotserver['depotId']
				break

		result.append(self._hashValuesNoneToEmptyString(hash))

	return result

def getClientIds_list(self, serverId=None, depotIds=[], groupId=None, productId=None, installationStatus=None, actionRequest=None, productVersion=None, packageVersion=None, hwFilter=None):
	return [client['hostId'] for client in self.getClients_listOfHashes(serverId, depotIds, groupId, productId, installationStatus, actionRequest, productVersion, packageVersion, hwFilter)]

def getClientIdByMac(self, mac):
	hosts = self.host_getObjects(attributes=['id'], type='OpsiClient', hardwareAddress=forceHardwareAddress(mac))
	if not hosts:
		return u''
	return hosts[0].id

def getServerIds_list(self):
	return self.host_getIdents(type = 'OpsiConfigserver')

def getServerId(self, clientId):
	return self.host_getIdents(type = 'OpsiConfigserver')[0]

def createDepot(self, depotName, domain, depotLocalUrl, depotRemoteUrl, repositoryLocalUrl, repositoryRemoteUrl, network, description=None, notes=None, maxBandwidth=0):
	id = forceHostId(forceHostname(depotName) + u'.' + forceDomain(domain))
	self.host_createOpsiDepotserver(
		id = id,
		depotLocalUrl = depotLocalUrl,
		depotRemoteUrl = depotRemoteUrl,
		repositoryLocalUrl = repositoryLocalUrl,
		repositoryRemoteUrl = repositoryRemoteUrl,
		description = description,
		notes = notes,
		hardwareAddress = None,
		ipAddress = None,
		networkAddress = network,
		maxBandwidth = maxBandwidth,
	)
	return id

def getDepotIds_list(self):
	return self.host_getIdents(type = 'OpsiDepotserver', isMasterDepot = True)

def getDepot_hash(self, depotId):
	depotId = forceHostId(depotId)
	depots = self.host_getObjects(id = depotId)
	if not depots:
		raise BackendMissingDataError(u"Depot '%s' not found" % depotId)
	hash = depots[0].toHash()
	del hash['type']
	if not hash['ipAddress']:
		try:
			hash['ipAddress'] = socket.gethostbyname(depotId)
		except Exception, e:
			logger.debug(u"Failed to get host by name (%s): %s" % (depotId, e))
	hash['ip'] = hash['ipAddress']
	return self._hashValuesNoneToEmptyString(hash)

def getDepotId(self, clientId=None):
	if not clientId:
		clientId = None

	for clientToDepotserver in self.configState_getClientToDepotserver(clientIds=clientId):
		if clientToDepotserver['clientId'] == clientId:
			return clientToDepotserver['depotId']
	raise BackendConfigurationError(u"Failed to get depot server for client '%s'" % clientId)

def deleteDepot(self, depotId):
	self.host_delete(id = forceHostId(depotId))

def generateOpsiHostKey(self):
	raise NotImplementedError("Not Implemented")

def getOpsiHostKey(self, hostId):
	if not hostId:
		raise NotImplementedError("No host id given")
	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(attributes = ['opsiHostKey'], id = hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host '%s' not found" % hostId)
	return hosts[0].opsiHostKey

def setOpsiHostKey(self, hostId, opsiHostKey):
	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(id = hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host '%s' not found" % hostId)
	hosts[0].setOpsiHostKey(opsiHostKey)
	self.host_updateObject(hosts[0])

def getMacAddresses_list(self, hostId):
	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(id = hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host '%s' not found" % hostId)
	if not hosts[0].hardwareAddress:
		return ['']
	return [ hosts[0].hardwareAddress ]

def setMacAddresses(self, hostId, macs=()):
	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(id = hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host '%s' not found" % hostId)
	hosts[0].setHardwareAddress(macs[0])
	self.host_updateObject(hosts[0])

def getMacAddress(self, hostId):
	return self.getMacAddresses_list(hostId)[0]

def setMacAddress(self, hostId, mac):
	self.setMacAddresses(hostId, macs = [ mac ] )

def lockProduct(self, productId, depotIds=[]):
	productOnDepots = self.productOnDepot_getObjects(productId = productId, depotId = depotIds)
	if not productOnDepots:
		raise BackendMissingDataError(u"Product '%s' not found on given depots" % productId)
	for productOnDepot in productOnDepots:
		productOnDepot.setLocked(True)
	self.productOnDepot_updateObjects(productOnDepots)

def unlockProduct(self, productId, depotIds=[]):
	productOnDepots = self.productOnDepot_getObjects(productId = productId, depotId = depotIds)
	if not productOnDepots:
		raise BackendMissingDataError(u"Product '%s' not found on given depots" % productId)
	for productOnDepot in productOnDepots:
		productOnDepot.setLocked(False)
	self.productOnDepot_updateObjects(productOnDepots)

def getProductLocks_hash(self, depotIds=[]):
	result = {}
	for productOnDepot in self.productOnDepot_getObjects(depotId = depotIds, locked = True):
		if not result.has_key(productOnDepot.productId):
			result[productOnDepot.productId] = []
		result[productOnDepot.productId].append(productOnDepot.depotId)
	return result

def createProduct(self, productType, productId, name, productVersion, packageVersion, licenseRequired=0,
		   setupScript="", uninstallScript="", updateScript="", alwaysScript="", onceScript="",
		   priority=0, description="", advice="", productClassNames=(), pxeConfigTemplate='',
		   windowsSoftwareIds=[], depotIds=[]):
	type = forceProductType(productType)
	hash = locals()
	del hash['productType']
	del hash['depotIds']
	del hash['self']
	hash['id'] = hash['productId']
	del hash['productId']

	product = Product.fromHash(hash)
	self.product_createObjects(product)

	productOnDepots = []
	if not depotIds:
		depotIds = self.host_getIdents(type = 'OpsiDepotserver')
	for depotId in depotIds:
		productOnDepots.append(
			ProductOnDepot(
				productId      = product.id,
				productType    = product.getType(),
				productVersion = product.productVersion,
				packageVersion = product.packageVersion,
				depotId        = depotId ))
	self.productOnDepot_createObjects(productOnDepots)

def createLocalBootProduct(self, productId, name, productVersion, packageVersion, licenseRequired=0,
		   setupScript="", uninstallScript="", updateScript="", alwaysScript="", onceScript="",
		   priority=0, description="", advice="", productClassNames=('localBoot'),
		   windowsSoftwareIds=[], depotIds=[]):
	self.createProduct('localboot', productId, name, productVersion, packageVersion, licenseRequired,
				setupScript, uninstallScript, updateScript, alwaysScript, onceScript,
				priority, description, advice, productClassNames, '',
				windowsSoftwareIds, depotIds)

def createNetBootProduct(self, productId, name, productVersion, packageVersion, licenseRequired=0,
		   setupScript="", uninstallScript="", updateScript="", alwaysScript="", onceScript="",
		   priority=0, description="", advice="", productClassNames=('netboot'), pxeConfigTemplate='',
		   windowsSoftwareIds=[], depotIds=[]):
	self.createProduct('netboot', productId, name, productVersion, packageVersion, licenseRequired,
				setupScript, uninstallScript, updateScript, alwaysScript, onceScript,
				priority, description, advice, productClassNames, pxeConfigTemplate,
				windowsSoftwareIds, depotIds)

def createServerProduct(self, productId, name, productVersion, packageVersion, licenseRequired=0,
		   setupScript="", uninstallScript="", updateScript="", alwaysScript="", onceScript="",
		   priority=10, description="", advice="", productClassNames=('server'),
		   windowsSoftwareIds=[], depotIds=[]):
	raise NotImplementedError("Not Implemented")

def deleteProduct(self, productId, depotIds=[]):
	self.productOnDepot_delete(productId = productId, depotId = depotIds)

def _productToHash(self, product):
	result = product.toHash()
	result['productId'] = result['id']
	del result['id']
	if (result['type'] == 'LocalbootProduct'):
		result['productType'] = 'localboot'
	elif (result['type'] == 'NetbootProduct'):
		result['productType'] = 'netboot'
	else:
		raise BackendBadValueError(u"Unknown product type '%s'" % result['type'])
	del result['type']
	return self._hashValuesNoneToEmptyString(result)

def getProduct_hash(self, productId, depotId=None):
	if not depotId:
		products = self.product_getObjects(id=productId)
		if not products:
			raise BackendMissingDataError(u"No product with id '%s' found" % productId)
		return self._productToHash(products[0])
	else:
		productOnDepot = self.productOnDepot_getObjects(productId=productId, depotId=depotId)
		if not productOnDepot:
			raise BackendMissingDataError(u"No product with id '%s' on depot '%s' found" % (productId, depotId))
		productOnDepot = productOnDepot[0]
		products = self.product_getObjects(
			id=productOnDepot.productId,
			productVersion=productOnDepot.productVersion,
			packageVersion=productOnDepot.packageVersion
		)
		if not products:
			raise BackendMissingDataError(u"Product with id '%s', productVersion '%s', packageVersion '%s' not found" \
				% (productOnDepot.productId, productOnDepot.productVersion, productOnDepot.packageVersion))
		return self._productToHash(products[0])

def getProducts_hash(self, depotIds=[]):
	from collections import defaultdict

	if not depotIds:
		depotIds = self.getDepotIds_list()

	products = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
	for product in self.product_getObjects():
		products[product.id][product.productVersion][product.packageVersion] = product

	result = defaultdict(dict)
	for productOnDepot in self.productOnDepot_getObjects(depotId=depotIds):
		product = products[productOnDepot.productId][productOnDepot.productVersion][productOnDepot.packageVersion]

		if not product:
			raise BackendMissingDataError(u"Product with id '%s', productVersion '%s', packageVersion '%s' not found" \
				% (productOnDepot.productId, productOnDepot.productVersion, productOnDepot.packageVersion))
		result[productOnDepot.depotId][productOnDepot.productId] = self._productToHash(product)

	return result

def getProducts_listOfHashes(self, depotId=None):
	if not depotId:
		return [self._productToHash(product) for product in self.product_getObjects()]

	result = []
	for (depId, products) in self.getProducts_hash(depotIds=[depotId]).items():
		for (productId, productHash) in products.items():
			result.append(productHash)
	return result

def getProductIds_list(self, productType=None, objectId=None, installationStatus=None):
	if not productType:        productType = None
	if not objectId:           objectId = None
	if not installationStatus: installationStatus = None
	type = None
	if   (productType == 'localboot'):
		type = 'LocalbootProduct'
	elif (productType == 'netboot'):
		type = 'NetbootProduct'
	productIds = []

	if objectId:
		objectId = forceHostId(objectId)
		hosts = self.host_getObjects(id = objectId)
		if not hosts:
			raise BackendMissingDataError(u"Host '%s' not found" % objectId)

		if isinstance(hosts[0], OpsiDepotserver):
			for ident in self.productOnDepot_getIdents(productType = type, depotId = hosts[0].getId(), returnType = 'dict'):
				productIds.append(ident['productId'])
			return productIds

		if not installationStatus:
			installationStatus = []

		for ident in self.productOnClient_getIdents(productType = type, clientId = hosts[0].getId(), installationStatus = installationStatus, returnType = 'dict'):
			productIds.append(ident['productId'])
		return productIds
	else:
		for ident in self.product_getIdents(type = type, returnType = 'dict'):
			if not ident['id'] in productIds:
				productIds.append(ident['id'])

	return productIds

def getLocalBootProductIds_list(self, objectId=None, installationStatus=None):
	return self.getProductIds_list('localboot', objectId, installationStatus)

def getNetBootProductIds_list(self, objectId=None, installationStatus=None):
	return self.getProductIds_list('netboot', objectId, installationStatus)

def getServerProductIds_list(self, objectId=None, installationStatus=None):
	raise NotImplementedError("Not Implemented")

def getInstallableProductIds_list(self, clientId):
	depotId = self.getDepotId(clientId = clientId)
	result = []
	for productOnDepot in self.productOnDepot_getObjects(depotId = depotId):
		result.append(productOnDepot.productId)
	return result

def getInstallableLocalBootProductIds_list(self, clientId):
	depotId = self.getDepotId(clientId = clientId)
	result = []
	for productOnDepot in self.productOnDepot_getObjects(depotId = depotId, productType = 'LocalbootProduct'):
		result.append(productOnDepot.productId)
	return result

def getInstallableNetBootProductIds_list(self, clientId):
	depotId = self.getDepotId(clientId = clientId)
	result = []
	for productOnDepot in self.productOnDepot_getObjects(depotId = depotId, productType = 'NetbootProduct'):
		result.append(productOnDepot.productId)
	return result

def getInstalledProductIds_list(self, objectId):
	result = []
	for productOnClient in self.productOnClient_getObjects(attributes = ['productId'], clientId = objectId, installationStatus = 'installed'):
		result.append(productOnClient.productId)
	return result

def getUninstalledProductIds_list(self, objectId):
	raise NotImplementedError("Not Implemented")

def getInstalledLocalBootProductIds_list(self, objectId):
	result = []
	for productOnClient in self.productOnClient_getObjects(clientId = objectId, productType = 'LocalbootProduct', installationStatus = 'installed'):
		result.append(productOnClient.productId)
	return result

def getInstalledNetBootProductIds_list(self, objectId):
	result = []
	for productOnClient in self.productOnClient_getObjects(clientId = objectId, productType = 'NetbootProduct', installationStatus = 'installed'):
		result.append(productOnClient.productId)
	return result

def getProvidedLocalBootProductIds_list(self, depotId):
	result = []
	for productOnDepot in self.productOnDepot_getObjects(depotId = depotId, productType = 'LocalbootProduct'):
		result.append(productOnDepot.productId)
	return result

def getProvidedNetBootProductIds_list(self, depotId):
	result = []
	for productOnDepot in self.productOnDepot_getObjects(depotId = depotId, productType = 'NetbootProduct'):
		result.append(productOnDepot.productId)
	return result

def getProductInstallationStatus_hash(self, productId, objectId):
	import re

	productId = forceProductId(productId)
	productOnClients = self.productOnClient_getObjects(productId = productId, clientId = objectId)
	if not productOnClients:
		return  {
			u"installationStatus": u"not_installed",
			u"productId": productId
		}
	hash = productOnClients[0].toHash()
	match = re.search('^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$', hash.get('modificationTime', ''))
	if match:
		hash['lastStateChange'] = u'%s%s%s%s%s%s' % ( match.group(1), match.group(2), match.group(3), match.group(4), match.group(5), match.group(6) )
	return hash

def getProductInstallationStatus_listOfHashes(self, objectId):
	import re

	depotId = self.getDepotId(clientId = objectId)
	products = {}
	for productOnDepot in self.productOnDepot_getObjects(depotId=depotId):
		products[productOnDepot.productId] = {
			u"lastStateChange":    u"",
			u"productVersion":     productOnDepot.productVersion,
			u"packageVersion":     productOnDepot.packageVersion,
			u"installationStatus": u"not_installed",
			u"productId":          productOnDepot.productId
		}

	for productOnClient in self.productOnClient_getObjects(clientId=objectId):
		lastStateChange = u""
		match = re.search('^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$', forceUnicode(productOnClient.modificationTime))
		if match:
			lastStateChange = u'%s%s%s%s%s%s' % ( match.group(1), match.group(2), match.group(3), match.group(4), match.group(5), match.group(6) )
		installationStatus = productOnClient.installationStatus
		if productOnClient.actionResult == "failed":
			installationStatus = u"failed"
		elif installationStatus == "unknown":
			installationStatus = u"not_installed"
		products[productOnClient.productId] = {
			u"lastStateChange":    lastStateChange,
			u"productVersion":     productOnClient.productVersion,
			u"packageVersion":     productOnClient.packageVersion,
			u"installationStatus": installationStatus,
			u"productId":          productOnClient.productId
		}
	return products.values()

def setProductState(self, productId, objectId, installationStatus="", actionRequest="", productVersion="", packageVersion="", lastStateChange="", productActionProgress={}):
	if not installationStatus:
		installationStatus = None
	if not actionRequest:
		actionRequest = None
	if not productVersion:
		productVersion = None
	if not packageVersion:
		packageVersion = None
	if not lastStateChange:
		lastStateChange = None
	if not productActionProgress:
		productActionProgress = None
	actionResult = None

	if actionRequest:
		productActionProgress = u""
		if actionRequest != 'none':
			actionResult = "none"

	if installationStatus:
		productActionProgress = u""
		if installationStatus == 'failed':
			actionResult = "failed"
			installationStatus = None
		elif installationStatus == 'installed':
			actionResult = "successful"
		elif installationStatus == 'installing':
			productActionProgress = installationStatus
			actionResult = "none"
			installationStatus = "unknown"

	depotId = self.getDepotId(clientId=objectId)
	productType = None
	for productOnDepot in self.productOnDepot_getObjects(depotId=depotId, productId=productId):
		productType = productOnDepot.productType

	if not productType:
		raise BackendMissingDataError("Product '%s' not found on depot '%s'" % (productId, depotId))

	self.productOnClient_updateObjects(
		ProductOnClient(
			productId=productId,
			productType=productType,
			clientId=objectId,
			installationStatus=installationStatus,
			actionRequest=actionRequest,
			actionProgress=productActionProgress,
			actionResult=actionResult,
			productVersion=productVersion,
			packageVersion=packageVersion,
			modificationTime=lastStateChange
		)
	)


def setProductInstallationStatus(self, productId, objectId, installationStatus):
	self.setProductState(
		productId=productId,
		objectId=objectId,
		installationStatus=installationStatus
	)


def setProductActionProgress(self, productId, hostId, productActionProgress):
	self.setProductState(
		productId=productId,
		objectId=hostId,
		productActionProgress=productActionProgress
	)

def getPossibleProductActions_list(self, productId=None, depotId=None):
	if not depotId: depotId = None
	result = [ u'none' ]
	if not productId:
		return [ u'none', u'setup', u'uninstall', u'update', u'always', u'once', u'custom' ]
	product = self.getProduct_hash(productId = productId, depotId = depotId)
	if product['setupScript']:     result.append(u'setup')
	if product['uninstallScript']: result.append(u'uninstall')
	if product['updateScript']:    result.append(u'update')
	if product['alwaysScript']:    result.append(u'always')
	if product['onceScript']:      result.append(u'once')
	if product['customScript']:    result.append(u'custom')
	return result

def getPossibleProductActions_hash(self, depotId=None):
	result = {}
	if not depotId or not depotId in self.getDepotIds_list():
		depotId = None
	for product in self.getProducts_listOfHashes(depotId = depotId):
		result[product['productId']] = [ u'none' ]
		if product['setupScript']:     result[product['productId']].append(u'setup')
		if product['uninstallScript']: result[product['productId']].append(u'uninstall')
		if product['updateScript']:    result[product['productId']].append(u'update')
		if product['alwaysScript']:    result[product['productId']].append(u'always')
		if product['onceScript']:      result[product['productId']].append(u'once')
		if product['customScript']:    result[product['productId']].append(u'custom')
	return result

def getProductActionRequests_listOfHashes(self, clientId, options=None):
	result = []
	for productOnClient in self.productOnClient_getObjects(clientId = clientId):
		result.append({
			u"productId":     productOnClient.productId,
			u"actionRequest": productOnClient.actionRequest
		})
	return result

def getDefaultNetBootProductId(self, clientId):
	raise NotImplementedError(u"Not Implemented")

def setProductActionRequest(self, productId, clientId, actionRequest):

	self.setProductState(
		productId     = productId,
		objectId      = clientId,
		actionRequest = actionRequest
	)

def unsetProductActionRequest(self, productId, clientId):
	self.setProductActionRequest(productId = productId, clientId = clientId, actionRequest = "none")

def _getProductStates_hash(self, objectIds=[], options={}, productType=None):
	import re
	from collections import defaultdict

	if not productType:
		productType = None

	result = defaultdict(list)

	modificationtimeRegex = re.compile('^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$')
	addProductOnClientDefaults = self._backend.backend_getOptions().get('addProductOnClientDefaults', False)
	try:
		self._backend.backend_setOptions({'addProductOnClientDefaults': True})
		for productOnClient in self.productOnClient_getObjects(clientId=objectIds, productType=productType):
			lastStateChange = productOnClient.modificationTime or u''

			match = modificationtimeRegex.search(lastStateChange)
			if match:
				lastStateChange = u'%s%s%s%s%s%s' % (match.group(1), match.group(2), match.group(3), match.group(4), match.group(5), match.group(6))

			installationStatus = productOnClient.installationStatus
			if productOnClient.actionResult == "failed":
				installationStatus = u"failed"
			elif installationStatus == "unknown":
				installationStatus = u"not_installed"

			result[productOnClient.clientId].append({
				u"lastStateChange": lastStateChange,
				u"productVersion": productOnClient.productVersion or u'',
				u"packageVersion": productOnClient.packageVersion or u'',
				u"installationStatus": installationStatus,
				u"actionRequest": productOnClient.actionRequest,
				u"productActionProgress": productOnClient.actionProgress or u'',
				u"productId": productOnClient.productId
			})
	finally:
		self._backend.backend_setOptions({'addProductOnClientDefaults': addProductOnClientDefaults})

	return result

def getLocalBootProductStates_hash(self, objectIds=[], options={}):
	return self._getProductStates_hash(objectIds=objectIds, options=options, productType='LocalbootProduct')

def getNetBootProductStates_hash(self, objectIds=[], options={}):
	return self._getProductStates_hash(objectIds=objectIds, options=options, productType='NetbootProduct')

def getProductStates_hash(self, objectIds=[], options={}):
	return self._getProductStates_hash(objectIds=objectIds, options=options)

def getProductPropertyDefinitions_hash(self, depotId=None):
	if not depotId: depotId = None
	result = {}
	propertyNames = {}

	productProperties = {}
	for productProperty in self.productProperty_getObjects():
		if not productProperties.has_key(productProperty.productId):
			productProperties[productProperty.productId] = {}
		if not productProperties[productProperty.productId].has_key(productProperty.productVersion):
			productProperties[productProperty.productId][productProperty.productVersion] = {}
		if not productProperties[productProperty.productId][productProperty.productVersion].has_key(productProperty.packageVersion):
			productProperties[productProperty.productId][productProperty.productVersion][productProperty.packageVersion] = []
		productProperties[productProperty.productId][productProperty.productVersion][productProperty.packageVersion].append(productProperty)

	depotProperties = {}
	if depotId:
		for productPropertyState in self.productPropertyState_getObjects(objectId = depotId):
			if not depotProperties.has_key(productPropertyState.productId):
				depotProperties[productPropertyState.productId] = {}
			depotProperties[productPropertyState.productId][productPropertyState.propertyId] = productPropertyState.values

	for productOnDepot in self.productOnDepot_getIdents(depotId = depotId, returnType = 'dict'):
		for productProperty in productProperties.get(productOnDepot['productId'], {}).get(productOnDepot['productVersion'], {}).get(productOnDepot['packageVersion'], []):
			productId = productProperty.getProductId()
			if not result.has_key(productId):
				result[productId] = []
			if not propertyNames.has_key(productId):
				propertyNames[productId] = {}
			if propertyNames[productId].has_key(productProperty.getPropertyId()):
				continue
			propertyNames[productId][productProperty.getPropertyId()] = 1

			defaults = productProperty.getDefaultValues()
			if depotId:
				defaults = depotProperties.get(productId, {}).get(productProperty.getPropertyId(), defaults)

			values = []
			if not productProperty.getEditable() or (productProperty.getPossibleValues() and len(productProperty.getPossibleValues()) > 1):
				values = forceUnicodeList(productProperty.getPossibleValues())
			result[productId].append({
				'name':        productProperty.getPropertyId(),
				'description': productProperty.getDescription(),
				'values':      values,
				'default':     u','.join(forceUnicodeList(defaults))
			})
	return result

def getProductPropertyDefinitions_listOfHashes(self, productId, depotId=None):
	result = []
	propertyNames = {}
	for productOnDepot in self.productOnDepot_getIdents(depotId = depotId, productId = productId, returnType = 'dict'):
		for productProperty in self.productProperty_getObjects(
					productId      = productOnDepot['productId'],
					productVersion = productOnDepot['productVersion'],
					packageVersion = productOnDepot['packageVersion']):
			if propertyNames.has_key(productProperty.getPropertyId()):
				continue
			propertyNames[productProperty.getPropertyId()] = 1

			defaults = productProperty.getDefaultValues()
			if depotId:
				productPropertyState = self.productPropertyState_getObjects(
								productId  = productId,
								propertyId = productProperty.getPropertyId(),
								objectId   = depotId)
				if productPropertyState:
					defaults = productPropertyState[0].values

			values = []
			if not productProperty.getEditable() or (productProperty.getPossibleValues() and len(productProperty.getPossibleValues()) > 1):
				values = forceUnicodeList(productProperty.getPossibleValues())
			result.append({
				'name':        productProperty.getPropertyId(),
				'description': productProperty.getDescription(),
				'values':      values,
				'default':     u','.join(forceUnicodeList(defaults))
			})
	return result

def deleteProductPropertyDefinition(self, productId, name, depotIds=[]):
	productProperties = []
	for productOnDepot in self.productOnDepot_getObjects(productId = productId, depotId = depotIds):
		productProperties.extend(
			self.productProperty_getObjects(
				productId      = productOnDepot.productId,
				productVersion = productOnDepot.productVersion,
				packageVersion = productOnDepot.packageVersion,
				propertyId     = name
			)
		)
	if productProperties:
		self.productProperty_deleteObjects(productProperties)

def deleteProductPropertyDefinitions(self, productId, depotIds=[]):
	productProperties = []
	for productOnDepot in self.productOnDepot_getObjects(productId = productId, depotId = depotIds):
		productProperties.extend(
			self.productProperty_getObjects(
				productId      = productOnDepot.productId,
				productVersion = productOnDepot.productVersion,
				packageVersion = productOnDepot.packageVersion
			)
		)
	if productProperties:
		self.productProperty_deleteObjects(productProperties)

def createProductPropertyDefinition(self, productId, name, description=None, defaultValue=None, possibleValues=[], depotIds=[]):
	productProperties = []
	productPropertyStates = []
	created = {}

	depotIds = self.host_getIdents(type = 'OpsiDepotserver', id = depotIds, returnType = 'unicode')
	for productOnDepot in self.productOnDepot_getObjects(productId = productId, depotId = depotIds):
		if productOnDepot.packageVersion in created.get(productOnDepot.productVersion, []):
			continue
		defaultValues = []
		if defaultValue:
			defaultValues = [ defaultValue ]
		editable = True
		if possibleValues:
			editable = False
		productProperties.append(
			UnicodeProductProperty(
				productId      = productOnDepot.productId,
				productVersion = productOnDepot.productVersion,
				packageVersion = productOnDepot.packageVersion,
				propertyId     = name,
				description    = description,
				possibleValues = possibleValues,
				defaultValues  = defaultValues,
				editable       = editable,
				multiValue     = False
			)
		)
		for depotId in depotIds:
			productPropertyStates.append(
				ProductPropertyState(
					productId  = productOnDepot.productId,
					propertyId = name,
					objectId   = depotId,
					values     = defaultValues
				)
			)
		if not created.has_key(productOnDepot.productVersion):
			created[productOnDepot.productVersion] = []
		created[productOnDepot.productVersion].append(productOnDepot.packageVersion)
	if productProperties:
		self.productProperty_createObjects(productProperties)
	if productPropertyStates:
		self.productPropertyState_createObjects(productPropertyStates)

def getProductProperties_hash(self, productId, objectId=None):
	result = {}
	if not objectId:
		for ppd in self.getProductPropertyDefinitions_listOfHashes(productId=productId):
			result[ppd['name']] = ppd['default']
		return result

	addProductPropertyStateDefaults = self._backend.backend_getOptions().get('addProductPropertyStateDefaults', False)
	try:
		self._backend.backend_setOptions({'addProductPropertyStateDefaults': True})
		for productPropertyState in self.productPropertyState_getObjects(productId=productId, objectId=objectId):
			result[productPropertyState.getPropertyId()] =  u','.join(forceUnicodeList(productPropertyState.getValues()))
	finally:
		self._backend.backend_setOptions({'addProductPropertyStateDefaults': addProductPropertyStateDefaults})

	return result

def setProductProperties(self, productId, properties, objectId=None):
	newProperties = {}
	for (propertyId, value) in properties.items():
		newProperties[forceProductPropertyId(propertyId)] = forceUnicode(value)
	properties = newProperties
	productPropertyStates = []
	if not objectId:
		depotIds = self.host_getIdents(type = 'OpsiDepotserver', returnType = 'unicode')
		for productPropertyState in self.productPropertyState_getObjects(
						productId  = productId,
						objectId   = depotIds):
			if not properties.has_key(productPropertyState.propertyId):
				continue
			productPropertyState.setValues(properties[productPropertyState.propertyId])
			productPropertyStates.append(productPropertyState)
	else:
		for (propertyId, value) in properties.items():
			productPropertyStates.append(
				ProductPropertyState(
					productId  = productId,
					propertyId = propertyId,
					objectId   = objectId,
					values     = [ value ]
				)
			)
	self.productPropertyState_createObjects(productPropertyStates)

def setProductProperty(self, productId, property, value, objectId=None):
	properties = self.getProductProperties_hash(productId, objectId)
	properties[property] = value
	self.setProductProperties(productId, properties, objectId)

def deleteProductProperty(self, productId, property, objectId=None):
	raise NotImplementedError(u"Not implemented")

def deleteProductProperties(self, productId, objectId=None):
	raise NotImplementedError(u"Not implemented")

def getProductDependencies_listOfHashes(self, productId=None, depotId=None):
	if not productId: productId = None
	if not depotId:   depotId = None
	result = []
	if depotId:
		productDependencies = {}
		for productDependency in self.productDependency_getObjects():
			if productId and productDependency.productId != productId:
				continue
			if not productDependencies.has_key(productDependency.productId):
				productDependencies[productDependency.productId] = {}
			if not productDependencies[productDependency.productId].has_key(productDependency.productVersion):
				productDependencies[productDependency.productId][productDependency.productVersion] = {}
			if not productDependencies[productDependency.productId][productDependency.productVersion].has_key(productDependency.packageVersion):
				productDependencies[productDependency.productId][productDependency.productVersion][productDependency.packageVersion] = []
			productDependencies[productDependency.productId][productDependency.productVersion][productDependency.packageVersion].append(productDependency)

		for productOnDepot in self.productOnDepot_getIdents(depotId = depotId, returnType = 'dict'):
			for productDependency in productDependencies.get(productOnDepot['productId'], {}).get(productOnDepot['productVersion'], {}).get(productOnDepot['packageVersion'], []):
				result.append({
					'productId':                  productDependency.getProductId(),
					'action':                     productDependency.getProductAction(),
					'requiredProductId':          productDependency.getRequiredProductId(),
					'requiredProductClassId':     u'',
					'requiredAction':             productDependency.getRequiredAction() or u'',
					'requiredInstallationStatus': productDependency.getRequiredInstallationStatus() or u'',
					'requirementType':            productDependency.getRequirementType() or u''
				})
	else:
		for productDependency in self.productDependency_getObjects(productId = productId):
			result.append({
				'productId':                  productDependency.getProductId(),
				'action':                     productDependency.getProductAction(),
				'requiredProductId':          productDependency.getRequiredProductId(),
				'requiredProductClassId':     u'',
				'requiredAction':             productDependency.getRequiredAction() or u'',
				'requiredInstallationStatus': productDependency.getRequiredInstallationStatus() or u'',
				'requirementType':            productDependency.getRequirementType() or u''
			})
	return result

def createProductDependency(self, productId, action, requiredProductId="", requiredProductClassId="", requiredAction="", requiredInstallationStatus="", requirementType="", depotIds=[]):
	if not requiredProductId:          requiredProductId = None
	if not requiredProductClassId:     requiredProductClassId = None
	if not requiredAction:             requiredAction = None
	if not requiredInstallationStatus: requiredInstallationStatus = None
	if not requirementType:            requirementType = None
	if not depotIds:                   depotIds = []
	for productOnDepot in self.productOnDepot_getObjects(productId = productId, depotId = depotIds):
		self.productDependency_create(
			productId                  = productOnDepot.productId,
			productVersion             = productOnDepot.productVersion,
			packageVersion             = productOnDepot.packageVersion,
			productAction              = action,
			requiredProductId          = requiredProductId,
			requiredProductVersion     = None,
			requiredPackageVersion     = None,
			requiredAction             = requiredAction,
			requiredInstallationStatus = requiredInstallationStatus,
			requirementType            = requirementType)

def deleteProductDependency(self, productId, action="", requiredProductId="", requiredProductClassId="", requirementType="", depotIds=[]):
	if not action:                     action = None
	if not requiredProductId:          requiredProductId = None
	if not requiredProductClassId:     requiredProductClassId = None
	if not requirementType:            requirementType = None
	if not depotIds:                   depotIds = []
	for productOnDepot in self.productOnDepot_getObjects(productId = productId, depotId = depotIds):
		self.productDependency_delete(
			productId         = productOnDepot.productId,
			productVersion    = productOnDepot.productVersion,
			packageVersion    = productOnDepot.packageVersion,
			productAction     = action,
			requiredProductId = requiredProductId,
			requirementType   = requirementType)

def createLicenseContract(self, licenseContractId="", partner="",
								conclusionDate="", notificationDate="",
								expirationDate="", notes=""):

	if not licenseContractId:
		# Generate license pool id
		import time
		knownLicenseContractIds = self.licenseContract_getIdents(returnType='unicode')
		t = time.strftime('%Y-%m-%d_%H:%M:%S', time.localtime())
		i = 0
		while True:
			licenseContractId = u'c_{time}_{index}'.format(time=t, index=i)
			if licenseContractId not in knownLicenseContractIds:
				break
			i += 1

	self.licenseContract_create(
		id=licenseContractId,
		description=None,
		notes=notes,
		partner=partner,
		conclusionDate=conclusionDate,
		notificationDate=notificationDate,
		expirationDate=expirationDate
	)

	return self.licenseContract_getIdents(id=licenseContractId, returnType='unicode')[0]

def getLicenseContractIds_list(self):
	return self.licenseContract_getIdents(returnType = 'unicode')

def getLicenseContract_hash(self, licenseContractId):
	licenseContracts = self.licenseContract_getObjects(id=licenseContractId)
	if not licenseContracts:
		raise BackendMissingDataError(u"License contract '%s' does not exist" % licenseContractId)
	hash = licenseContracts[0].toHash()

	# Cut time part, date only
	if hash['conclusionDate']:
		hash['conclusionDate'] = hash['conclusionDate'].split(' ')[0]
	else:
		hash['conclusionDate'] = u''

	if hash['notificationDate']:
		hash['notificationDate'] = hash['notificationDate'].split(' ')[0]
	else:
		hash['notificationDate'] = u''

	if hash['expirationDate']:
		hash['expirationDate'] = hash['expirationDate'].split(' ')[0]
	else:
		hash['expirationDate'] = u''

	hash['licenseContractId'] = hash['id']
	del hash['id']
	del hash['type']
	return hash

def getLicenseContracts_listOfHashes(self):
	licenseContracts = []
	for licenseContract in self.licenseContract_getObjects():
		hash = licenseContract.toHash()
		# Cut time part, date only
		if hash['conclusionDate']:
			hash['conclusionDate'] = hash['conclusionDate'].split(' ')[0]
		else:
			hash['conclusionDate'] = u''

		if hash['notificationDate']:
			hash['notificationDate'] = hash['notificationDate'].split(' ')[0]
		else:
			hash['notificationDate'] = u''

		if hash['expirationDate']:
			hash['expirationDate'] = hash['expirationDate'].split(' ')[0]
		else:
			hash['expirationDate'] = u''

		hash['licenseContractId'] = hash['id']
		del hash['id']
		del hash['type']
		licenseContracts.append(hash)

	return licenseContracts

def deleteLicenseContract(self, licenseContractId):
	self.licenseContract_delete(id = licenseContractId)

def createSoftwareLicense(self, softwareLicenseId="", licenseContractId="", licenseType="", maxInstallations="", boundToHost="", expirationDate=""):
	if not boundToHost:
		boundToHost = None
	if not expirationDate:
		expirationDate = None
	if not licenseType:
		licenseType = 'volume'
	if not softwareLicenseId:
		# Generate software license id
		import time
		knownSoftwareLicenseIds = self.softwareLicense_getIdents(returnType = 'unicode')
		t = time.strftime('%Y-%m-%d_%H:%M:%S', time.localtime())
		i=0
		while True:
			softwareLicenseId = u'l_%s_%d' % (t, i)
			if not softwareLicenseId in knownSoftwareLicenseIds:
				break
			i+=1
	method = None
	if   (str(licenseType).lower() == 'oem'):
		method = self.softwareLicense_createOEM
	elif (str(licenseType).lower() == 'retail'):
		method = self.softwareLicense_createRetail
	elif (str(licenseType).lower() == 'volume'):
		method = self.softwareLicense_createVolume
	elif (str(licenseType).lower() == 'concurrent'):
		method = self.softwareLicense_createConcurrent
	else:
		raise BackendBadValueError(u"Unknown license type '%s'" % licenseType)
	method( id                = softwareLicenseId,
		licenseContractId = licenseContractId,
		maxInstallations  = maxInstallations,
		boundToHost       = boundToHost,
		expirationDate    = expirationDate)
	return self.softwareLicense_getIdents(id = softwareLicenseId, returnType = 'tuple')[0][0]

def getSoftwareLicenseIds_list(self):
	ids = []
	for ident in self.softwareLicense_getIdents(returnType = 'unicode'):
		ids.append(ident[0])
	return ids

def getSoftwareLicense_hash(self, softwareLicenseId):
	softwareLicenses = self.softwareLicense_getObjects(id = softwareLicenseId)
	if not softwareLicenses:
		raise BackendMissingDataError(u"Software license '%s' does not exist" % softwareLicenseId)
	hash = softwareLicenses[0].toHash()
	if   isinstance(softwareLicenses[0], OEMSoftwareLicense):        hash['licenseType'] = 'OEM'
	elif isinstance(softwareLicenses[0], RetailSoftwareLicense):     hash['licenseType'] = 'RETAIL'
	elif isinstance(softwareLicenses[0], VolumeSoftwareLicense):     hash['licenseType'] = 'VOLUME'
	elif isinstance(softwareLicenses[0], ConcurrentSoftwareLicense): hash['licenseType'] = 'CONCURRENT'
	# Cut time part, date only
	if hash['expirationDate']: hash['expirationDate'] = hash['expirationDate'].split(' ')[0]
	else:                      hash['expirationDate'] = u''
	if not hash['boundToHost']: hash['boundToHost'] = u''
	hash['softwareLicenseId'] = hash['id']
	del hash['id']
	del hash['type']

	hash['licenseKeys'] = {}
	hash['licensePoolIds'] = []
	for softwareLicenseToLicensePool in self.softwareLicenseToLicensePool_getObjects(softwareLicenseId = softwareLicenseId):
		hash['licensePoolIds'].append(softwareLicenseToLicensePool.getLicensePoolId())
		if softwareLicenseToLicensePool.getLicenseKey():
			hash['licenseKeys'][softwareLicenseToLicensePool.getLicensePoolId()] = softwareLicenseToLicensePool.getLicenseKey()
	return hash

def getSoftwareLicenses_listOfHashes(self):
	softwareLicenses = []
	for softwareLicense in self.softwareLicense_getObjects():
		hash = softwareLicense.toHash()
		if   isinstance(softwareLicense, OEMSoftwareLicense):        hash['licenseType'] = 'OEM'
		elif isinstance(softwareLicense, RetailSoftwareLicense):     hash['licenseType'] = 'RETAIL'
		elif isinstance(softwareLicense, VolumeSoftwareLicense):     hash['licenseType'] = 'VOLUME'
		elif isinstance(softwareLicense, ConcurrentSoftwareLicense): hash['licenseType'] = 'CONCURRENT'
		# Cut time part, date only
		if hash['expirationDate']: hash['expirationDate'] = hash['expirationDate'].split(' ')[0]
		else:                      hash['expirationDate'] = u''
		if not hash['boundToHost']: hash['boundToHost'] = u''
		hash['softwareLicenseId'] = hash['id']
		del hash['id']
		del hash['type']

		hash['licenseKeys'] = {}
		hash['licensePoolIds'] = []
		for softwareLicenseToLicensePool in self.softwareLicenseToLicensePool_getObjects(softwareLicenseId = softwareLicense.getId()):
			hash['licensePoolIds'].append(softwareLicenseToLicensePool.getLicensePoolId())
			if softwareLicenseToLicensePool.getLicenseKey():
				hash['licenseKeys'][softwareLicenseToLicensePool.getLicensePoolId()] = softwareLicenseToLicensePool.getLicenseKey()

		softwareLicenses.append(hash)
	return softwareLicenses

def deleteSoftwareLicense(self, softwareLicenseId, removeFromPools=False):
	if removeFromPools:
		self.softwareLicenseToLicensePool_delete(softwareLicenseId = softwareLicenseId)
	self.softwareLicense_delete(id = softwareLicenseId)

def createLicensePool(self, licensePoolId="", description="", productIds=[], windowsSoftwareIds=[]):
	if not licensePoolId:
		# Generate license pool id
		import time
		knownLicensePoolIds = self.licensePool_getIdents(returnType = 'unicode')
		t = time.strftime('%Y-%m-%d_%H:%M:%S', time.localtime())
		i=0
		while True:
			licensePoolId = u'p_%s_%d' % (t, i)
			if not licensePoolId in knownLicensePoolIds:
				break
			i+=1
	self.licensePool_create(
		id                 = licensePoolId,
		description        = description,
		productIds         = productIds)
	if windowsSoftwareIds:
		auditSoftwareToLicensePools = []
		for auditSoftware in self.auditSoftware_getObjects(windowsSoftwareId = forceUnicodeList(windowsSoftwareIds)):
			auditSoftwareToLicensePools.append(
				AuditSoftwareToLicensePool(
					name          = self.auditSoftware.name,
					version       = self.auditSoftware.version,
					subVersion    = self.auditSoftware.subVersion,
					language      = self.auditSoftware.language,
					architecture  = self.auditSoftware.architecture,
					licensePoolId = licensePoolId
				)
			)
		self.auditSoftwareToLicensePool_createObjects(auditSoftwareToLicensePools)

	poolIdents = self.licensePool_getIdents(id = licensePoolId, returnType = 'tuple')
	if not poolIdents:
		raise Exception(u"Failed to create license pool")
	return poolIdents[0][0]

def getLicensePoolIds_list(self):
	return self.licensePool_getIdents(returnType = 'unicode')

def getLicensePool_hash(self, licensePoolId):
	licensePools = self.licensePool_getObjects(id = licensePoolId)
	if not licensePools:
		raise BackendMissingDataError(u"License pool '%s' does not exist" % licensePoolId)
	hash = licensePools[0].toHash()
	hash['licensePoolId'] = hash['id']
	del hash['id']
	del hash['type']
	hash['windowsSoftwareIds'] = []
	for auditSoftwareToLicensePool in self.auditSoftwareToLicensePool_getObjects(licensePoolId = licensePools[0].id):
		auditSoftwares = self.auditSoftware_getObjects(
			['windowsSoftwareId'],
			name         = auditSoftwareToLicensePool.name,
			version      = auditSoftwareToLicensePool.version,
			subVersion   = auditSoftwareToLicensePool.subVersion,
			language     = auditSoftwareToLicensePool.language,
			architecture = auditSoftwareToLicensePool.architecture,
		)
		if not auditSoftwares:
			continue
		if not auditSoftwares[0].windowsSoftwareId:
			continue
		hash['windowsSoftwareIds'].append(auditSoftwares[0].windowsSoftwareId)
	return hash

def getLicensePools_listOfHashes(self):
	licensePools = []

	auditSoftwareToLicensePoolsByLicensePoolId = {}
	for auditSoftwareToLicensePool in self.auditSoftwareToLicensePool_getObjects():
		if not auditSoftwareToLicensePoolsByLicensePoolId.has_key(auditSoftwareToLicensePool.licensePoolId):
			auditSoftwareToLicensePoolsByLicensePoolId[auditSoftwareToLicensePool.licensePoolId] = []
		auditSoftwareToLicensePoolsByLicensePoolId[auditSoftwareToLicensePool.licensePoolId].append(auditSoftwareToLicensePool)

	for licensePool in self.licensePool_getObjects():
		hash = licensePool.toHash()
		hash['licensePoolId'] = hash['id']
		del hash['id']
		del hash['type']
		hash['windowsSoftwareIds'] = []
		for auditSoftwareToLicensePool in auditSoftwareToLicensePoolsByLicensePoolId.get(licensePool.id, []):
			auditSoftwares = self.auditSoftware_getObjects(
				['windowsSoftwareId'],
				name         = auditSoftwareToLicensePool.name,
				version      = auditSoftwareToLicensePool.version,
				subVersion   = auditSoftwareToLicensePool.subVersion,
				language     = auditSoftwareToLicensePool.language,
				architecture = auditSoftwareToLicensePool.architecture,
			)
			if not auditSoftwares:
				continue
			if not auditSoftwares[0].windowsSoftwareId:
				continue
			hash['windowsSoftwareIds'].append(auditSoftwares[0].windowsSoftwareId)
		licensePools.append(hash)
	return licensePools

def deleteLicensePool(self, licensePoolId, deleteLicenses=False):
	if deleteLicenses:
		for ident in self.softwareLicenseToLicensePool_getIdents(licensePoolId = licensePoolId, returnType = 'dict'):
			self.licenseOnClient_delete(softwareLicenseId = ident['softwareLicenseId'], licensePoolId = licensePoolId, clientId = [])
			self.softwareLicense_delete(ident['softwareLicenseId'])
	self.licensePool_delete(id = licensePoolId)

def addSoftwareLicenseToLicensePool(self, softwareLicenseId, licensePoolId, licenseKey=""):
	self.softwareLicenseToLicensePool_create(
		softwareLicenseId = softwareLicenseId,
		licensePoolId     = licensePoolId,
		licenseKey        = licenseKey)

def removeSoftwareLicenseFromLicensePool(self, softwareLicenseId, licensePoolId):
	self.softwareLicenseToLicensePool_delete(
		softwareLicenseId = softwareLicenseId,
		licensePoolId     = licensePoolId)

def addProductIdsToLicensePool(self, productIds, licensePoolId):
	productIds = forceUnicodeList(productIds)
	licensePools = self.licensePool_getObjects(id = licensePoolId)
	if not licensePools:
		raise BackendMissingDataError(u"License pool '%s' does not exist" % licensePoolId)
	productIds.extend(licensePools[0].getProductIds())
	licensePools[0].setProductIds(productIds)
	self.licensePool_updateObject(licensePools[0])

def removeProductIdsFromLicensePool(self, productIds, licensePoolId):
	productIds = forceUnicodeList(productIds)
	licensePools = self.licensePool_getObjects(id = licensePoolId)
	if not licensePools:
		raise BackendMissingDataError(u"License pool '%s' does not exist" % licensePoolId)
	newProductIds = []
	for productId in licensePools[0].getProductIds():
		if not productId in productIds:
			newProductIds.append(productId)
	licensePools[0].setProductIds(newProductIds)
	self.licensePool_updateObject(licensePools[0])

def setWindowsSoftwareIdsToLicensePool(self, windowsSoftwareIds, licensePoolId):
	windowsSoftwareIds = forceUnicodeList(windowsSoftwareIds)
	licensePoolIds = self.licensePool_getIdents(id = licensePoolId, returnType = 'unicode')
	if not licensePoolIds:
		raise BackendMissingDataError(u"License pool '%s' does not exist" % licensePoolId)

	self.auditSoftwareToLicensePool_delete(
					name          = [],
					version       = [],
					subVersion    = [],
					language      = [],
					architecture  = [],
					licensePoolId = licensePoolIds)

	auditSoftwareToLicensePools = []
	for auditSoftware in self.auditSoftware_getObjects(windowsSoftwareId = forceUnicodeList(windowsSoftwareIds)):
		auditSoftwareToLicensePools.append(
			AuditSoftwareToLicensePool(
				name          = auditSoftware.name,
				version       = auditSoftware.version,
				subVersion    = auditSoftware.subVersion,
				language      = auditSoftware.language,
				architecture  = auditSoftware.architecture,
				licensePoolId = licensePoolId
			)
		)
	self.auditSoftwareToLicensePool_createObjects(auditSoftwareToLicensePools)

def getLicensePoolId(self, productId="", windowsSoftwareId=""):
	if not productId and not windowsSoftwareId:
		raise BackendBadValueError(u"Neither product id nor windows software id given.")
	idents = []
	if productId:
		productId = forceProductId(productId)
		idents = self.licensePool_getIdents(productIds = productId, returnType = 'unicode')
	elif windowsSoftwareId:
		windowsSoftwareId = forceUnicode(windowsSoftwareId)

		auditSoftwares = self.auditSoftware_getObjects(windowsSoftwareId = windowsSoftwareId)
		for auditSoftware in auditSoftwares:
			auditSoftwareToLicensePools = self.auditSoftwareToLicensePool_getObjects(
							name         = auditSoftware.name,
							version      = auditSoftware.version,
							subVersion   = auditSoftware.subVersion,
							language     = auditSoftware.language,
							architecture = auditSoftware.architecture
			)
			if auditSoftwareToLicensePools:
				idents.append(auditSoftwareToLicensePools[0].licensePoolId)
	if (len(idents) < 1):
		raise LicenseConfigurationError(u"No license pool for product id '%s', windowsSoftwareId '%s' found" % (productId, windowsSoftwareId))
	elif (len(idents) > 1):
		raise LicenseConfigurationError(u"Multiple license pools for product id '%s', windowsSoftwareId '%s' found" % (productId, windowsSoftwareId))
	return idents[0]

def getOrCreateSoftwareLicenseUsage_hash(self, hostId, licensePoolId="", productId="", windowsSoftwareId=""):
	return self.licenseOnClient_getOrCreateObject(
			clientId          = hostId,
			licensePoolId     = licensePoolId,
			productId         = productId,
			windowsSoftwareId = windowsSoftwareId).toHash()

def getAndAssignSoftwareLicenseKey(self, hostId, licensePoolId="", productId="", windowsSoftwareId=""):
	licenseKey = ''
	try:
		if not licensePoolId:
			licensePoolId = self.getLicensePoolId(productId=productId, windowsSoftwareId=windowsSoftwareId)
		return self.getOrCreateSoftwareLicenseUsage_hash(hostId, licensePoolId, productId, windowsSoftwareId).get('licenseKey', '')
	except Exception as exception:
		logger.warning(u"Failed to get license key from license management for host '%s', pool '%s', product '%s', softwareid '%s': %s" \
				% (hostId, licensePoolId, productId, windowsSoftwareId, exception))

		# Trying the old way - product keys as product property
		if productId:
			properties = self.getProductProperties_hash(productId=productId, objectId=hostId)
			try:
				licenseKey = properties['productkey']
			except KeyError:
				# No productkey found - we continue.
				pass

		if not licenseKey:
			raise exception

	return licenseKey

def getLicenseKey(self, productId, clientId):
	'''
	Returns an unused licensekey if available or
	the license key assigend to a specific client
	! do not use ! deprecated !
	'''
	return self.getAndAssignSoftwareLicenseKey(hostId = clientId, productId = productId)

def getSoftwareLicenseUsages_listOfHashes(self, hostIds=[], licensePoolIds=[]):
	if not hostIds:        hostIds = []
	if not licensePoolIds: licensePoolIds = []
	licenseOnClients = []
	for licenseOnClient in self.licenseOnClient_getObjects(licensePoolId = licensePoolIds, clientId = hostIds):
		hash = licenseOnClient.toHash()
		hash['hostId'] = hash['clientId']
		if hash['licenseKey'] is None:
			hash['licenseKey'] = u''
		if hash['notes'] is None:
			hash['notes'] = u''
		del hash['clientId']
		licenseOnClients.append(hash)
	return licenseOnClients

def setSoftwareLicenseUsage(self, hostId, licensePoolId, softwareLicenseId, licenseKey="", notes=""):
	self.licenseOnClient_create(
		softwareLicenseId = softwareLicenseId,
		licensePoolId     = licensePoolId,
		clientId          = hostId,
		licenseKey        = licenseKey,
		notes             = notes)
	return { 'hostId' :  hostId, 'softwareLicenseId': softwareLicenseId, 'licensePoolId' : licensePoolId }

def deleteSoftwareLicenseUsage(self, hostId, softwareLicenseId="", licensePoolId="", productId="", windowsSoftwareId=""):
	if not softwareLicenseId:
		softwareLicenseId = None
	if not licensePoolId:
		licensePoolId = self.getLicensePoolId(productId = productId, windowsSoftwareId = windowsSoftwareId)
	self.licenseOnClient_delete(
		softwareLicenseId = softwareLicenseId,
		licensePoolId     = licensePoolId,
		clientId          = hostId)

def deleteAllSoftwareLicenseUsages(self, hostIds):
	self.licenseOnClient_delete(clientId = hostIds)

def getLicenseStatistics_hash(self):
	result = {}
	for licensePool in self.licensePool_getObjects():
		poolId = licensePool.getId()
		licenses = 0
		maxInstallations = 0
		remainingInstallations = 0
		additionalLicensePoolIds = set()

		for softwareLicenseToLicensePool in self.softwareLicenseToLicensePool_getObjects(licensePoolId=poolId):
			for softwareLicenseToLicensePool2 in self.softwareLicenseToLicensePool_getObjects(softwareLicenseId=softwareLicenseToLicensePool.getSoftwareLicenseId()):
				if softwareLicenseToLicensePool2.getLicensePoolId() == poolId:
					continue
				additionalLicensePoolIds.add(softwareLicenseToLicensePool2.getLicensePoolId())

			licenses += 1
			allowedInstallations = self.softwareLicense_getObjects(attributes=['maxInstallations'], id=softwareLicenseToLicensePool.getSoftwareLicenseId())[0].getMaxInstallations()
			if allowedInstallations == 0:
				maxInstallations = 'infinite'
			elif maxInstallations != 'infinite':
				maxInstallations += allowedInstallations

		usedBy = [licenseOnClient.getClientId()
					for licenseOnClient
					in self.licenseOnClient_getObjects(attributes=['clientId'], licensePoolId=poolId)]

		if maxInstallations == 'infinite':
			remainingInstallations = 'infinite'
		else:
			remainingInstallations = maxInstallations - len(usedBy)
			if additionalLicensePoolIds:
				poolIds = [additionalPoolId for additionalPoolId in additionalLicensePoolIds]
				licensesUsedByClientsInAdditionalPools = self.licenseOnClient_getIdents(licensePoolId=poolIds)
				remainingInstallations = remainingInstallations - len(licensesUsedByClientsInAdditionalPools)

			if remainingInstallations < 1:
				remainingInstallations = 0

		result[poolId] = {
			'licenses': licenses,
			'usedBy': usedBy,
			'usageCount': len(usedBy),
			'maxInstallations': maxInstallations,
			'remainingInstallations': remainingInstallations
		}

	return result

def getMD5Sum(self, filename):
	return self.depot_getMD5Sum(filename)

def librsyncSignature(self, filename):
	return self.depot_librsyncSignature(filename)

def getDiskSpaceUsage(self, path):
	return self.depot_getDiskSpaceUsage(path)

def getHostRSAPublicKey(self):
	return self.depot_getHostRSAPublicKey()

def getPcpatchRSAPrivateKey(self):
	rsaPrivateKey = self.user_getCredentials(username = 'pcpatch')['rsaPrivateKey']
	if not rsaPrivateKey:
		raise BackendMissingDataError(u"RSA private key not found")
	return rsaPrivateKey

def getPcpatchPassword(self, hostId):
	return self.user_getCredentials(username = 'pcpatch', hostId = hostId)['password']

def setPcpatchPassword(self, hostId, password):
	if not hostId in self.getDepotIds_list():
		return
	return self.user_setCredentials(username = 'pcpatch', password = password)

def userIsAdmin(self):
	if self.accessControl_userIsAdmin():
		return True
	raise Exception(u"User not admin")

def areDepotsSynchronous(self, depotIds=[]):
	depotIds = self.host_getIdents(type = 'OpsiDepotserver', id = depotIds, returnType = 'unicode')
	if not depotIds:
		raise Excpetion(u"No depots found")
	if (len(depotIds) == 1):
		return True

	lastIdent = ''
	for i, currentDepot in enumerate(depotIds):
		idents = []
		for ident in self.productOnDepot_getIdents(depotId=currentDepot, returnType='dict'):
			idents.append(u'%s;%s;%s' % (ident['productId'], ident['productVersion'], ident['packageVersion']))
		idents.sort()
		ident = u'|'.join(idents)
		if (i > 0) and (ident != lastIdent):
			return False
		lastIdent = ident
	return True

def setHostInventoryNumber(self, hostId, inventoryNumber):
	hostId = forceHostId(hostId)
	hosts = self.host_getObjects(id=hostId)
	if not hosts:
		raise BackendMissingDataError(u"Host {0!r} not found".format(hostId))
	host = hosts[0]
	host.setInventoryNumber(inventoryNumber)
	self.host_updateObject(host)
