#!/usr/bin/env python
# -*- coding: utf-8 -*-

# $Id: bacresources.py 12826 2011-04-03 16:49:56Z pstorz $ 

import time

#from directive import *
#from configrules import *
from auto_configrules import *

import logging
from auto_onlinehelp import *
logger = logging.getLogger('bacresources')

import os
import tarfile
import tempfile
import glob
import shutil
import string
import random

#TODO: we have a problem if inside of a password there is a special character like {}


def genPw(self,length=32, chars=string.letters + string.digits):
  # from http://code.activestate.com/recipes/59873-random-password-generation/
  return ''.join([random.choice(chars) for i in range(length)])




def config2blocks(configstring,  processEventsCallback = None):
    """
    Parses the configstring and returns a list of blocks.
    Each block is represented by its start- and end characterindex
    saved in 'start' and 'end'
    the configstring is expected without comment lines
    """
    blockdepth = 0
    blockstack = []
    blocks = []
    instring = False # are we in a string between (") ?
    lastcharbackslash = False

    for i in range(0, len(configstring)):
      
      #if processEventsCallback is not None and not (i%100):
      ##  #print "calling processEventsCallback"
      #  processEventsCallback(i)
        
      chr =  configstring[i]
      if chr == '\\':
        lastcharbackslash = True
        #print 'foundbackslash'
      else:
        lastcharbackslash = False
        
      if chr == '"' and not lastcharbackslash:  # we may have a brace in a password, but can escape with \"
        instring = not instring                 # TODO:password with { or } inside this still make problems!
        
      if chr == '{' and not instring:
        blockdepth += 1
        #print '\n ',blockdepth,' **{** ->',configstring[i-10:i+20],'<-',
        b = {}
        b['start'] = i
        b['level'] = blockdepth
        blockstack.append(b)  
            
      elif chr == '}' and not instring:
        blockdepth -= 1
        b = blockstack.pop()
        b['end'] = i
        #print '\n ', blockdepth , ' **}** ->',configstring[i-10:i+1],'<-'
        # find name just before the block beginning
        s = configstring[:b['start']] 
        m = RXP_RESTYPE.search(s)
        #logger.debug( "searching for ResType in >" + s + "<" )
        if m:
          b['namestart'] = b['start'] - len(m.group('resourcetype')) - 1 # start of resname......{ instead of {
          b['name'] = m.group('resourcetype').strip(' =\n').replace(' ','') # there are also resname = {xxx}
          #b['name'] = m.group('resourcetype').strip(' =\n') # there are also resname = {xxx}
          if b['name'].endswith('\n'):
            print '>>>>>>>>>>>>>>>>>>>>>>>>><>' + b['name'] + '<'
          blocks.append(b)
        else:
          b['name'] = 'name not found!'
          logger.error('Problem parsing the config file, regex did not match')
          
    if blockdepth != 0:
      # TODO: throw exception
      print "unbalanced braces in config2blocks"
      return "unbalanced braces in config2blocks"
    return blocks
 



'''
Class for ressources inside of configurations
hierarchy: 
Config -> Resource -> Items+
                  |-> Resource -> Items
'''

class Resource(object):
  """
  This class represents a bacula resource .
  On creation, it is automatically filled with the possible configuration items
  for the specific resource type.
  Some items can exist multiple times (like file=, plugin= etc.), 
  These are created one time and set to their default value 
  """
  def __init__(self, parentconfig, resourcetype, indentlevel = 1, printall = False, onlinehelpURL = ''):
  #def __init__(self, parentconfig, resourcetype,  name = None, indentlevel = 1, printall = False):
    self.onlinehelpURL = onlinehelpURL
    self.indentlevel = indentlevel
    #print resourcetype, type(self.indentlevel) ,self.indentlevel
    self.shortname = res2shrtn[resourcetype]
    self.parentconfig = parentconfig # what configuration does this resource belong to?
    #print "my parentconfig is" + repr(parentconfig)
    self.printall = printall
    self.resources = []
    self.items = []
    self.reflist = [] # what resources reference this resource?
    if resourcetype == 'runscript':
      items = eval ( "dird_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
      self.items = copy.deepcopy(items)
      logging.debug('eval: ' + " dird_"+ res2shrtn[resourcetype] + "_items[:]")  
    elif resourcetype == 'messages':
      items = eval ( "lib_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
      self.items = copy.deepcopy(items)
      logging.debug('eval: ' + " lib_"+ res2shrtn[resourcetype] + "_items[:]")
    elif resourcetype == 'options':
      items = eval ( "inc_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
      self.items = copy.deepcopy(items)
      logging.debug('eval: ' + "inc_"+ res2shrtn[resourcetype] + "_items[:]")
    else:  
      #print resourcetype
      #self.items = eval ( self.parentconfig.shortname+"_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
      items = eval ( self.parentconfig.shortname+"_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
      self.items = copy.deepcopy(items)
      logging.debug('eval: ' + self.parentconfig.shortname+"_"+ res2shrtn[resourcetype] + "_items[:]" )
    
    #for item in self.items:
    #  logger.debug(item.name + ' id: '+ str(id(item)))
    #  if item.name in MULTIPLE_ALLOWED_ITEMS_SET:
    #    del item
        
      
    
    # set parent resource for all own items
    for item in self.items:
      item.parentresource = self
      item.indentlevel = 1
      
    
    
    self.__create_valid_items_set__()
    self.__create_items_dict___()
    self.resourcetype = resourcetype # director, filedaemon ...
    #self.name = name                 # bacula-dir, bacula-fd ...
    #self.items.sort()
    logger.debug('Created ressource ' +  resourcetype + ' with id #' + str(id(self)) )

  def getResourcesListByResType(self,restype):
    """
    returns a list of resources of the specified resource type.
    """
    reslist = []
    if restype == 'client' or restype == 'filedaemon':
      for res in self.resources:
        if res.resourcetype == 'client' or res.resourcetype == 'filedaemon':
          reslist.append(res)
    else:  
      for res in self.resources:
        if res.resourcetype == restype:
          reslist.append(res) 
    return reslist
  
  def getItemsListByItemName(self,itemname):
    '''
    returns a list of items of the specified item name
    '''
    itemlist = []
    for item in self.items:
      if item.name == itemname:
        itemlist.append(item)
    return itemlist


  def getName(self):
    '''
    gets the value of the item "name"
    '''
    if self.items_dict.has_key('name'):
      return self.items_dict['name'].storage.value.strip('"')
    else:
      return ''
    
  def setName(self,name):
    '''
    gets the value of the item "name"
    '''
    if self.items_dict.has_key('name'):
      self.items_dict['name'].setValue(name)
    else:
      print "could not set name " + name   

  def getPassword(self):
    '''
    gets the value of the item "password"
    '''
    if self.items_dict.has_key('password'):
      return self.items_dict['password'].storage.value.strip('"')
    else:
      return "ERROR: no password defined"
    
  def getItemValue(self, itemname):
    '''
    gets the value of the itemname
    '''
    if self.items_dict.has_key(itemname):
      return self.items_dict[itemname].storage.value
    else:
      return "ERROR: no item " + itemname + "defined"
  
  def setItemValue(self, itemname, newValue):
    '''
    gets the value of the itemname
    '''
    if self.items_dict.has_key(itemname):
      self.items_dict[itemname].setValue(newValue)
    else:
      print "ERROR: no item " + itemname + "defined"

  def __create_valid_items_set__(self):
    """
    create a set of the valid items for this resource.
    """
    self.validitemsset = set()
    for item in self.items:
      self.validitemsset.add(item.name)
  
  def __create_items_dict___(self):
    """
    create a dict with the name of the items as key
    for easier access
    """
    self.items_dict = {}
    for item in self.items:
      self.items_dict[item.name] = item
      item.indentlevel = self.indentlevel + 1
    
  def __str__(self):
    """
    create a string representation of the resource
    including subresources and items
    """
    if self.resourcetype in RESSOURCES_WITH_EQUALSIGN:
      s = "%s%s = { \n" % (self.indentlevel * INDENTSTRING, prettyName(self.resourcetype))#, self.__class__.__name__ +' id: '+ str(id(self)) )
    else:
      s = "%s%s { \n" % (self.indentlevel * INDENTSTRING, prettyName(self.resourcetype))#, self.__class__.__name__ +' id: '+ str(id(self)) )
    

    
    # print own items
    for item in self.items:
      item.printall = self.printall
      if str(item) != '':
        s +=  str(item)
        
    # print subresources
    for v in self.resources:
      s +=   str(v)
              
    s += '%s}\n'  % ( self.indentlevel * INDENTSTRING )
    
    return s
  
  def parse_string(self, configstring):
    '''
    parse resource configuration string and 
    set the resource items accordingly.
    '''
    logger.debug( self.resourcetype +  " : parse_string called with \n\"" + configstring + '\"' )

    #TODO REMOVE THE FOUND SUBBLOCK FROM THE ANALYSIS INSIND OF THE PARENTBLOCK: 
    # search for subblocks    
    block_to_check = configstring
    blocks = config2blocks(configstring)
    for block in blocks:
      if block['level'] == 1:
        # find containing blocks and only parse the block without the subblocks (resources)
        #logger.debug( "found block:" + str(block['start']) + ':' + str(block['end']) )
        #logger.debug( "block now  :" + block_to_check )
        block_to_del = configstring[block['namestart']:block['end']+1]
        logger.debug( "delete:"+ block_to_del + 'in' + block_to_check)
        tmp = block_to_check.replace(block_to_del,'')  # remove the subblock/resource
        block_to_check = tmp
        
        #logger.debug(self.resourcetype +':' + block['name'].lower() )
        #                parentconfig, resourcetype,  name = None, indentlevel = 1, printall = False
        newres = Resource(self, block['name'].lower(), self.indentlevel + 1 )
        if newres != None:
          newres.parse_string(configstring[block['start']+1:block['end']])
          #newres.parse_string(configstring[block['start']+1:block['end']])
          self.resources.append(newres)
          logger.debug( "just appended resource :" + str(id(newres)) )
        else:
          logger.error('did not get a resource back, maybe resource not allowed in this configuration?')

    
    ''' find all occurrences of "simple" item = value lines'''
    for line in RXP_SIMPLE_ITEM.finditer(block_to_check):
      logger.debug("checking for simple_items: " +block_to_check)
      #logger.debug( line.groups())
      logger.debug('SIMPLE_ITEM_MATCH:*' + self.resourcetype + '* >' + line.group('itemname') + '<    >' + line.group('value') + '<')
      normalized_itemname = line.group('itemname').strip().lower().replace(' ','') 
      #logger.debug(str(id(self))+ '*' + str(self))
      if normalized_itemname  not in self.validitemsset:
        logger.error( normalized_itemname + ' not allowed in this ressource')
      else:
        logger.debug( normalized_itemname + ' ok in this ressource')
        
        #if normalized_itemname == 'commandacl':
        #  self.items_dict[normalized_itemname].ACLDict = commands.keys();
          
        if normalized_itemname not in  MULTIPLE_ALLOWED_ITEMS_SET:
          self.items_dict[normalized_itemname].setValue(line.group('value').strip())
      
        
        elif normalized_itemname == 'file':
          newitem = Item('file', 0, 0, False, 'store_dir', False, indentlevel = self.indentlevel + 1) # "File" Items can exist multiple times
          logger.debug( 'created new  \"' + normalized_itemname + '\" Item with id #' + str(id(newitem)) )
          newitem.setValue(line.group('value'))
          self.items.append(newitem)
          newitem.parentresource = self
          #logger.debug('appended #' + str(id(newitem)) + str(newitem) + ' to #' + str(id(self)) + self.name + ' ' + self.resourcetype)
          #logger.debug(self.items)
        elif normalized_itemname == 'run':
          newitem =  Item('run', '', 0, False, 'store_run', False, indentlevel = self.indentlevel + 1) # "run" Items can exist multiple times
          logger.debug( 'created new  \"' + normalized_itemname + '\" Item with id #' + str(id(newitem)) )
          newitem.setValue(line.group('value'))
          self.items.append(newitem)
          newitem.parentresource = self
 
        else:
          logger.debug( 'please create  logic for multiple item type  ' + normalized_itemname ) 
        
        
    



class BaculaConfig(object):
  '''
  base class for bacula config file (console, dir, sd, fd)
  if processEventsCallback is given,it is called at regular basis to enable the
  update of the gui.
  '''
  def __init__(self, configAsString, parentdatacenter = None, processEventsCallback = None, filename = None): 
    self.filename = filename
    self.parentdatacenter = parentdatacenter
    self.validresources = eval(self.shortname+'_resources') # TODO: Try/Except
    #self.onlinehelp = {}
    if not self.resources: 
      self.resources = []
    
    self.name = '' # Name of director, filedaemon, storage daemon, console
    self.__create_validresources_set__()
    
    if len(configAsString) > 0 :              # if configstring is not empty
      self.parse_configfile(configAsString, processEventsCallback)
    else:     
      for r in self.validresourcesset:       # if configstring is empty -> initialize needed resources
        if (self.__class__.__name__ == 'FileDaemonConfig' 
            and r == 'client'): # Filedaemon and Client are the same, we only need one
          continue
        logger.debug("initializing new resource:" + r)
        self.createResource(r)
    
    logger.debug( 'Created BaculaConfig ' + self.__class__.__name__ )
    
  def getName(self):
    '''
    get name from own main resource:
    in director config the name of the director resource,
    other resource files respectively
    '''  
    mainresources = self.getResourcesListByResType(self.longname)
    logger.debug(mainresources, self.longname)
    #assert len(mainresources) < 2 
    # this assertion can break on console.confs, where multiple consoles can be defined.
    # TODO: check this
    # we only take the name of the first defined console, maybe we should show all names?
    if len(mainresources) == 0:
      return ''
    elif len(mainresources) == 1:
      return mainresources[0].getName()
    else:
      namelist = []
      for res in mainresources:
        namelist.append(res.getName())
      return ','.join(namelist)
      
      

  def getPassword(self):
    '''
    get password from own main resource:
    in director config the name of the director resource,
    other resource files respectively
    '''  
    mainresources = self.getResourcesListByResType(self.longname)
    logger.debug(mainresources, self.longname)
    #assert len(mainresources) < 2 
    # this assertion can break on console.confs, where multiple consoles can be defined.
    # TODO: check this
    # we only take the name of the first defined console, maybe we should show all names?
    if len(mainresources) == 0:
      return ''
    else:
      #return mainresources[0].items_dict['name'].storage.value
      return mainresources[0].getPassword()

  def getResourcesListByResType(self,restype):
    """
    returns a list of resources of the specified resource type.
    """
    reslist = []
    for res in self.resources:
      if res.resourcetype == restype:
        reslist.append(res)
    return reslist
  
  def getResourceByName(self,name):
    """
    returns a list of resources with name in parameter "name"
    """
    for res in self.resources:
      #print res.items_dict['name'].storage.value , name 
      if str(res.items_dict['name'].storage.value).strip('"') == name.strip('"'):
        return res
    return None
  
  def getReferencingResourcesListForResource(self,resource):
    """
    return a list of resources referencing the given resource
    """
    reslist = []
    for res in self.resources:
      for item in res.items:
        referredType = item.name
        if referredType.endswith('pool'): #incrementalpool, differentialpool, etc
          #print "setting referredType to pool as", referredType
          referredType = 'pool'
        #print "checking:", referredType, resource.resourcetype
        
        # check for overrides in run entry for references
        if referredType == 'run':
          if hasattr(item.storage,'runentry'):# check for runentry in schedules
            #print item.storage.runentry.overrides
            for ov,val in item.storage.runentry.overrides.iteritems():
              #print "checking: ",ov,val
              if val is None:
                continue
              #if ov.endswith('pool'):
              #  ovcheck = 'pool'
              #else:
              ovcheck = val
              #print "OVERRIDE_CHECKING",ovcheck,resource.items_dict['name'].storage.value
              if ovcheck == resource.items_dict['name'].storage.value:
                #print "FOUND_OVERRIDE:", ovcheck,resource.items_dict['name'].storage.value
                reslist.append(res)
              
        if referredType == resource.resourcetype:
          #print "found:",item.name, referredType, resource.resourcetype
          #print "comparing", str(item.storage.value).strip('"'), str(resource.items_dict['name'].storage.value).strip('"')
          if item.storage.value != None:
            if str(item.storage.value).strip('"') == resource.items_dict['name'].storage.value.strip('"'):
              #print "FOUND:", item.storage.value, resource.items_dict['name'].storage.value
              reslist.append(res)
              #print item.name,item.storage.value, resource.resourcetype, resource.items_dict['name'].storage.value
          
          
          #if item.value == resource.items_dict['name']:
          
    return reslist      
   
  def parse_configfile(self, configstring, callback):
    """
    parses the configstring: removes the comment lines,
    """
 
    cf = ''
    for line in configstring.splitlines(True):
      cf += line[:line.find('#')]+'\n'    # remove comments
      
    blocks = config2blocks(cf)
    
    #b = 1
    for block in blocks:
      #print "%d of %d blocks" % (b, len(blocks))
      #b+=1
      if block['level'] == 1:
        logger.debug( block['name'].lower() )
        # TODO: can we call Resource() directly?
        
        newres = self.createResource( block['name'].lower() )
        if callback is not None:
            callback("creating new " + block['name'].lower() + " resource" ) # call callback function after every resource
        if newres != None:
          newres.parse_string(cf[block['start']+1:block['end']])
          if callback is not None:
            callback(newres.getName()) # call callback function after every resource
        else:
          logger.error('did not get a ressource back, maybe ressource not allowed in this configuration?') 


     
  def __str__(self):
    """
    creates the string representation of the configuration file
    by recursively calling the string representation of all resources
    """
    s = '' # string representation
    now = time.asctime()
    
    # set verid string to the date 
    # TODO: Make this configurable
    #dirconfs = self.getResourcesListByResType('director')
    #
    #for d in dirconfs:
    #  try:
    #    d.items_dict['verid'].storage.setValue('" | configuration file created on :' + now + '"' )
    #  except:
    #    logger.error('verid setting impossible, maybe object not yet initialized?')
    
    
        
    s += '# config file written on ' + now + '\n'
    for v in self.resources:
      s +=  str(v)
      s += "\n"
    return s
  
  
  def selfcheck(self): 
    """ 
    selfcheck for valid conditions
    TODO documentation
    """
    retval = True
    logger.debug(self.__class__.__name__ + "Selfcheck starts")
    # check if defined resources are allowed
    for res in self.resources:
      if res.resourcetype not in self.validresourcesset:
        logger.error( "Error: resource name " + res.resourcetype + " not allowed")
        retval =  False
      else:
        logger.debug(res.resourcetype + " is allowed")
    
    # check if at least one of the needed resources is defined
    for res in self.validresourcesset:
      if self.getResourcesListByResType(res) == None:
        logger.error("Resourctype " + " is missing!")
        retval = False
      else:
        logger.debug(res + " is configured, OK")
    logger.debug(self.__class__.__name__ + "Selfcheck end")
    return retval
      
    

  def createResource(self, resourcetype ):
    ''' 
    creates a ressource of type "ressourcetype" 
    inside of this bacula configuration 
    '''
    if resourcetype not in self.validresourcesset:
      logger.error("ressource type \"" + resourcetype + "\" not allowed in this configuration")
    else:
      if self.onlinehelp.has_key(resourcetype):
        helpurl = self.onlinehelp[resourcetype]
      else:
        helpurl = ''
      newres = Resource( self, resourcetype, indentlevel = 0, onlinehelpURL = helpurl)
      self.resources.append( newres )
      
      return newres

  def deleteResource(self, resource ):
    '''
    delete given resource from own resources
    '''
    try:
      self.resources.remove(resource)
      retval = True
    except:
      logger.error("could not remove resource")
      retval = False 
    return retval
   
    
  
  def __create_validresources_set__(self):
    ''' 
    Create a set of allowed ressources
    inside of this bacula configuration 
    '''
    self.validresourcesset = set()
    for validresource in self.validresources:
      self.validresourcesset.add(validresource[0]) # add name
    #print self.validresourcesset

    


class DirectorConfig(BaculaConfig):
  '''
  Class for configuration of one director ( bacula-dir.conf )
  ''' 
  def __init__(self, configAsString, parentdatacenter = None, processEventsCallback = None, filename = None):
    self.shortname = 'dird'
    self.longname  = 'director'
    self.onlinehelp = onlinehelp[self.longname]
    self.resources = []
    BaculaConfig.__init__(self, configAsString, parentdatacenter, processEventsCallback, filename)
       


class StorageDaemonConfig(BaculaConfig):
  '''
  Class for configuration of one storage daemon ( bacula-sd.conf )
  ''' 
  def __init__(self, configAsString, parentdatacenter = None, processEventsCallback = None, filename = None):
    self.shortname = 'stored'
    #self.longname  = 'storagedaemon'
    self.longname  = 'storage'
    self.onlinehelp = onlinehelp['storagedaemon']
    self.resources = []
    BaculaConfig.__init__(self, configAsString, parentdatacenter, processEventsCallback, filename)


class FileDaemonConfig(BaculaConfig):
  '''
  Class for configuration of one storage daemon ( bacula-fd.conf )
  ''' 
  def __init__(self, configAsString,  parentdatacenter = None, processEventsCallback = None, filename = None):
    self.shortname = 'filed'
    self.longname  = 'filedaemon'
    self.onlinehelp = onlinehelp['client']
    self.resources = []
    BaculaConfig.__init__(self, configAsString, parentdatacenter , processEventsCallback, filename)
  
  
  
  def createFdConfigFromDirClient(self, dirClient = None):
    ''' 
    create a Filedaemon Config from a 
    Client Config in Director
    we have to set some required values to defaults
    TODO: we should be able to use different defaults for 
    different classes of clients (windows, linux, solaris etc.)
    '''
    
    directors = self.getResourcesListByResType('director')
    if len(directors) != 1: 
      logger.error("here we should have one director!")
      return None
    
    messages = self.getResourcesListByResType('messages')
    if len(messages) != 1: 
      logger.error("here we should have one messages!")
      return None
      
    filedaemons = self.getResourcesListByResType('filedaemon') # same as client
    if len(filedaemons) != 1: 
      logger.error("here we should have one filedaemon!")
      return None
      
    dirDirector = dirClient.parentconfig.getResourcesListByResType('director')[0]
      
    # get password and dirname from director config     
    directors[0].items_dict['name'].storage.value     = dirDirector.items_dict['name'].storage.value 
    directors[0].items_dict['password'].storage.value = dirClient.items_dict['password'].storage.value
    filedaemons[0].items_dict['name'].storage.value   = dirClient.items_dict['name'].storage.value
    
    self.name = filedaemons[0].items_dict['name'].storage.value
    
    # TODO: make the following configurable
    filedaemons[0].items_dict['workingdirectory'].storage.value = '/var/lib/bacula'
    filedaemons[0].items_dict['piddirectory'].storage.value = '/var/run'
    
    messages[0].items_dict['name'].storage.value = 'Standard'
    messagesentry =  directors[0].items_dict['name'].storage.value + ' = all, !skipped, !restored'
    messages[0].items_dict['director'].storage.setValue(messagesentry)
    #messages[0].items_dict['director'].storage.value = directors[0].items_dict['name'].storage.value + ' = all, !skipped, !restored'
     
    #filedaemons[0].items_dict['password'].storage.value = dirClient.items_dict['password'].storage.value
    
    #dirClient.parentconfig.getResourceByName(parentname).items_dict['name']
    
    

    #self.items_dict['password'] = DirDirector.items_dict['password']
    


class ConsoleConfig(BaculaConfig):
  '''
  Class for configuration of a console ( bconsole.conf )
  ''' 
  def __init__(self, configAsString, parentdatacenter = None, processEventsCallback = None, filename = None):
    self.shortname = 'console'
    self.longname  = 'console'
    self.onlinehelp = onlinehelp[self.longname]
    self.resources = []
    BaculaConfig.__init__(self, configAsString, parentdatacenter, processEventsCallback, filename)






class DataCenter(object):
  '''
  A datacenter is the sum of all configurations that 
  make a running bacula system, i.e. director, storaged, 
  console and filed configurations
  A datacenter resides in an own subdirectory
  and has subdirs for consoles, directors, storagedaemons and filedaemons
  /datacenter/
  /datacenter/consoles/
  /datacenter/directors/
  /datacenter/storagedaemons/
  /datacenter/filedaemons/
  
  this directory structure is hidden inside of a tar.gz file 
  '''
  
  def __createDataCenterDirs__(self, basedir):
    olddir = os.getcwdu()
    os.mkdir(basedir)
    #os.chdir(basedir)
    for subdir in self.subdirs:
      os.mkdir( os.path.normpath(basedir+'/'+subdir) )
    #os.chdir(olddir)

 
  
  def __init__(self, targzfile, callbackfunction = None):
    '''
    extract the targz file, 
    open the *.conf file inside the
    subdirs and create the configurations accordingly  
    '''
    self.filename = targzfile
    self.subdirs = ['consoles','directors','storagedaemons','filedaemons']
    self.directors = set()
    self.consoles = set()
    self.storagedaemons = set()
    self.filedaemons = set()
    tmpdir = tempfile.mkdtemp(prefix = 'dassmodus-')
    # TODO: delete this after use

    # if the targz file does not exist, then create an empty 
    # datacenter file
    if not os.path.isfile(targzfile):
      print targzfile + ' does not exist, creating new file'
      self.name='datacenter'
      os.mkdir(tmpdir + '/' + self.name)# datacentername
      for subdir in self.subdirs:
        os.mkdir(tmpdir + '/' + self.name + '/' + subdir)

      # create a new tarfile  
      tar = tarfile.open(targzfile, "w:gz")
      dirs = glob.glob(os.path.normpath(tmpdir + '/*/*'))
      for d in dirs:
        tar.add(d, arcname = d.replace(tmpdir + '/',''))
      tar.close()


    # extract subdirs inside of the tarfile into tmp dir
    tar = tarfile.open(targzfile, "r:gz")

    #olddir = os.getcwdu()
    #os.chdir(self.tmpdir)
    tar.extractall(path = tmpdir)
    tar.close

    basedir = os.listdir(tmpdir)
    datacenterdir = os.path.normpath(tmpdir + '/' + basedir[0])
    #os.chdir(basedir[0]) # change into the
    
    self.name = basedir[0]



    for subdir in self.subdirs:
      files = glob.glob(os.path.normpath(datacenterdir + '/' + subdir + '/*.conf' ) )
      for filename in files:
        if callbackfunction is not None:
          callbackfunction('parsing file ' + filename)
        
        if subdir == 'directors':
          configstring = open(filename).read()
          conf = DirectorConfig(configstring, parentdatacenter = self, filename = filename, processEventsCallback = callbackfunction )
          self.directors.add(conf)

        elif subdir == 'storagedaemons':
          configstring = open(filename).read()
          conf = StorageDaemonConfig(configstring, parentdatacenter = self,  filename = filename, processEventsCallback = callbackfunction)
          self.storagedaemons.add(conf)

        elif subdir == 'filedaemons':
          configstring = open(filename).read()
          conf = FileDaemonConfig(configstring, parentdatacenter = self,  filename = filename ,processEventsCallback = callbackfunction)
          self.filedaemons.add(conf)

        elif subdir == 'consoles':
          configstring = open(filename).read()
          conf = ConsoleConfig(configstring, parentdatacenter = self,  filename = filename, processEventsCallback = callbackfunction)
          self.consoles.add(conf)
        else:
          raise NameError('help me')
    
  
    # remove temporary files
    shutil.rmtree(tmpdir)
    #os.chdir(olddir) # restore working dir

   
  def getName(self):
    return self.name
  
  def setName(self, name):
    self.name = name
  
  def setFileName(self, filename):
    self.filename = filename
    self.name = os.path.basename(filename).split('.')[0]
    
  def importConfigurationFile(self, filename, configtype):
    '''
    import a existing configuration file into this
    datacenter.
    allowed configtypes: filedaemon, storagedaemon, console, director
    '''
    print "importing file " + filename + ' ' + configtype
    if configtype == 'filedaemon':
      configstring = open(filename).read()
      conf = FileDaemonConfig(configstring, filename = filename)
      self.filedaemons.add(conf)

    elif configtype == 'storagedaemon':
      configstring = open(filename).read()
      conf = StorageDaemonConfig(configstring, filename = filename)
      self.storagedaemons.add(conf)

    elif configtype == 'console':
      configstring = open(filename).read()
      conf = ConsoleConfig(configstring,  filename = filename)
      self.consoles.add(conf)

    elif configtype == 'director':
      configstring = open(filename).read()
      conf = DirectorConfig(configstring, filename = filename)
      self.directors.add(conf)

    else:
      raise NameError('Unknown configtype, allowed : filedaemon, storagedaemon, console, director')
      return False


  def deleteConfiguration(self, configuration):
    '''
    delete the refered configuration resource/configfile
    '''
    for configtype in [self.directors, self.filedaemons, self.storagedaemons, self.consoles]:
      if configuration in configtype:
        configtype.remove(configuration)
        del configuration
        break
      
     
  
  
  def checkFileDaemonsIntegrity(self):
    '''
    Possible configuration problems:
      1a: - dirconf refers to filedaemon, but no filedaemon conf exists
      1b: - dirconf refers to filedaemon, but filedaemon conf has wrong password for this director
      2a: - filedaemon conf refers to Director that does not exist
      2b: - filedaemon conf refers to existing Director, but password is wrong for this director  
      1b and 2b are the same
     
    this leads to 
     3 cases: 
     - Dir does not exist, 
     - Filedaemon does not exist, 
     - Passwords do not match
    '''
    
    s = '' # return string
    director_filedaemons_passwords  = {}          #  dictionary of director -> filedaemonname -> password, extracted from dirconfs
    filedaemons_directors_passwords = {}          #  dictionary of filedaemon -> director -> password , extracted from fdconfs
    
    # collect info about all defined filedaemons/clients in ALL Director Configurations
    for dir in self.directors:
      dirfds = dir.getResourcesListByResType('client')
      director_filedaemons_passwords[dir.getName()] = {}
      for fd in dirfds:
        director_filedaemons_passwords[dir.getName()][fd.getName()] = fd.getPassword() 
    
    # collect info about all definied Directors in all Filedaemon Configurations 
    for fdconf in self.filedaemons:
      filedaemons_directors_passwords[fdconf.getName()] = {} 
      for dir in fdconf.getResourcesListByResType('director'):
        filedaemons_directors_passwords[fdconf.getName()][dir.getName()] =  dir.getPassword()
    
    #print "Filedaemon View:"
    # check from the view of filedaemons configurations
    for f,d in filedaemons_directors_passwords.iteritems():
      for dir,pw in d.iteritems():
        #print f,dir,pw
        if director_filedaemons_passwords.has_key(dir):        # do we have a director config of this director?
          if  director_filedaemons_passwords[dir].has_key(f):  # has this dir a definition of this fd? 
            if director_filedaemons_passwords[dir][f] == pw:   # and is the password identical?
              s += "OK: Filedaemon %s has definition of Director %s and password is the same\n" % (f,dir)
              #s += "INFO: Filedaemon %s has definition of Director %s and password is the same: %s\n" % (f,dir,pw)
            else:
              s+= "ERROR: Filedaemon %s has definition of Director %s but password is different: %s != %s\n" % (f,dir,pw,director_filedaemons_passwords[dir][f])              
          else:
            s+= "ERROR: Filedaemon %s has not a definition of Director %s\n" % (dir,f)
        else:
          s+= "ERROR: Director configuration for %s is MISSING , it was referenced by configfile of %s\n" % (dir,f)                                     
                                      
    #print "Director View:"
    # check from the view of director configurations
    for d,f in director_filedaemons_passwords.iteritems():
      for fs,pwd in f.iteritems():
        #print d,fs,pwd
        if filedaemons_directors_passwords.has_key(fs):       # do we have a fd-conf for this filedaemon?  
          if filedaemons_directors_passwords[fs].has_key(d):  # does this fd-conf have a reference to this dir?              
            if filedaemons_directors_passwords[fs][d] == pwd: # is the password identical?
              s+= "OK: Director %s is known by Filedaemon %s and passwords are the same.\n" %(d, fs)
              #s+= "OK: Director %s is known by Filedaemon %s and passwords are the same: %s\n" %(d, fs, pwd)
            else:
              s+= "ERROR: Director %s is known by Filedaemon %s, but passwords are NOT the same: %s != %s\n" % (d, fs, pwd,filedaemons_directors_passwords[fs][d])      
          else:
            s+= "WARNING: Director %s does not reference Filedaemon %s,that may be OK\n" % (dir, fs)
            # Not every Dir has to reference every Filedaemon
        else:
          s+= "ERROR: fd-conf is MISSING for Filedaemon %s :  please import or create.\n" %(fs)

    return s

    
  
  
      
  def checkStorageDaemonsIntegrity(self):
    '''
    Possible configuration problems:
      1a: - dirconf refers to storage, but no storagedaemon conf exists with this storage
      1b: - dirconf refers to storage, storagedaemon has this storage but has wrong password for this director
     
      2a: - StorageDaemon conf refers to Director that does not exist
      2b: - StorageDaemon conf refers to existing Director, but password is wrong for this director  
     
      3a: - dirconf refers to Device or Autochanger that is not defined in Storage Daemon
      3b: - Storage Daemon defines Device or Autochanger that is not used by any Director 
            (info, not problem, may be used in a autochanger)
      4a: - dirconf refers to Device or Autochanger that is defined, but the *MediaType* is not the same 
    '''
    
    '''
    Datastructures we need:
     
    # Director view
    directorName - storageName - password
                               - Device
                               - Media Type
                               - AutoChanger?
    
    # Storage view
    storageName - directorName - Password
                - DeviceName - MediaType
                - AutochangerName[Devices] 
    '''
  
    director_storages = {}
    storage_directors = {}
    
    # collect info about all defined storages in ALL Director Configurations
    for dir in self.directors:
      dirstorages = dir.getResourcesListByResType('storage')
      director_storages[dir.getName()] = {}
      for storage in dirstorages:
        director_storages[dir.getName()]['password'] = storage.getPassword() 
        director_storages[dir.getName()]['device'] = storage.getItemValue('device') 
        director_storages[dir.getName()]['mediatype'] = storage.getItemValue('mediatype')
    
    # collect info about all definied Directors in all Filedaemon Configurations 
    for sdconf in self.storagedaemons:
      storage_directors[sdconf.getName()] = {}
       
      for dir in sdconf.getResourcesListByResType('director'):
        storage_directors[sdconf.getName()][dir.getName()] =  dir.getPassword()
             
      for device in sdconf.getResourcesListByResType('device'):
        storage_directors[sdconf.getName()][device.getName()] =  device.getItemValue('mediatype')
        
      for autochanger in sdconf.getResourcesListByResType('autochanger'):
        storage_directors[sdconf.getName()][autochanger.getName()] =  device.getItemValue('device')
            
      '''
      TODO: implement the checks and output
      TODO: implement test cases for this function!
      '''    

  
  def checkConsolesIntegrity(self):
    '''
    check if we have a console conf for every main dir in each dirconf (passwds)
    and if we have a console for every named console in each dir (names)
    including passwords check
    '''
    '''
    possible configuration problems:
    - no console.conf for director entry in director configuration (check passwds)
    - no console.conf for named console entries in director configurations
    - console.conf for named console entry but passwords not the same
    - console.conf, whose password does not match name/password of named consoles and 
      not password of anonymous console
    - console.conf contains console entry that refers a director that is not defined inside of this console conf 
    
    Datastructures we need:
    # director view:
    directorname - mainpassword              # anonymous/default console
                 - consolename - password    # named console
    # console view:
    console - directorname - password        # anonymous/default console
            - consolename - directorname     # named console, refers to defined director inside of this configfile
    # consoles can have multiple director entries, and multiple console entries referring these directors        
    '''
    
    directors_consoles = {}
    consoles_directors = {}
    
    for dir in self.directors: # look in all directors of our DataCenter
      dirname = dir.getName()
      defaultconsolepassword = dir.getPassword() 
      #dir.getResourcesListByResType('director')[0].getPassword()  # we can only have 1 director entry per director conf
      directors_consoles[dirname]={}
      directors_consoles[dirname]['defaultconsole'] = defaultconsolepassword
      
      for console in dir.getResourcesListByResType('console'):
        directors_consoles[dirname][console.getName()] = console.getPassword()
    
    
    defconsoles = 0  
    for consoleconf in self.consoles: # check all consoles in DC
      #print consoleconf
      consolename = consoleconf.getName()
      if consolename == '':  # we have an defaultconsole
        defconsoles += 1
        consolename = '__defaultconsole__%d' % (defconsoles)
      consoles_directors[consolename]={}
      consoles_directors[consolename]['directors']= {}
      consoles_directors[consolename]['consoles']  = {}
      
      for dir in consoleconf.getResourcesListByResType('director'):
        consoles_directors[consolename]['directors'][dir.getName()] = dir.getPassword()
        
      for con in consoleconf.getResourcesListByResType('console'):
        consoles_directors[consolename]['consoles'][con.getItemValue('director')] = {}
        consoles_directors[consolename]['consoles'][con.getItemValue('director')]['name'] = con.getName()
        consoles_directors[consolename]['consoles'][con.getItemValue('director')]['password'] = con.getPassword()
    print "break"
    
    
    s = ''
    # check the directorconfig view
    for dir in directors_consoles:
      defaultpw = directors_consoles[dir]['defaultconsole']
      for consolename, dirinfo in consoles_directors.iteritems(): # console resource in console config
        unreferred_dirs = set() # main directors in dirconfs -> Default Consoles
        if consolename.startswith('__defaultconsole__'): # we have a default console
          for d,pw in dirinfo['directors'].iteritems():
            unreferred_dirs.add(d) # remember dirs
            if d == dir:
              unreferred_dirs.remove(d)
              s += "found defaultconsole in console.conf for dir %s" % ( d )
              if defaultpw == pw:
                s += " and passwords are the same"
              else:
                s += " and passwords are different!"
            else:
              s+= "default console not configured for this dir"
              
            #print out unreferred main dir confs    
            for unrefdir in unreferred_dirs:
              s+= "director %s not referred by any console.conf as default console" % (unrefdir)
              
        else: # named console
          print "found named console" ,consolename
          for c, consdir in dirinfo['consoles'].iteritems():
            print c, consdir
             
          for d,pw in dirinfo['directors'].iteritems():
            #print d,pw
            print d, pw, dirinfo['consoles'][d]['password']
            #print consolename, dirinfo, 
            try:
              print directors_consoles[dir][d]
            except:
              print "no named console for dir " , d  
              
               
            
            # check if we have 
            
            
            
            
            #unreferred_dirs.add(d) # remember dirs
            if d == dir:
              unreferred_dirs.remove(d)
              s += "found defaultconsole in console.conf for dir %s" % ( d )
              if defaultpw == pw:
                s += " and passwords are the same"
              else:
                s += " and passwords are different!"
            else:
              s+= "default console not configured for this dir"
              
            #print out unreferred main dir confs    
            for unrefdir in unreferred_dirs:
              s+= "director %s not referred by any console.conf as default console" % (unrefdir)
          
          
          
        #for conscons in consoles_directors
        #  print conscons
          #referred_dirs.add(conscons[dir])
    
        #for consdir in console['directors']: 
      ''' now we have collected all info about defined dirs and consoles in this Datacenter
       Checks: 
        Director view
       A: iterate over all directors, 
       I:check if we have a anonymous console conf that refers to the given password
       II:iterate over every named console: 
           1.: do we have a director entry in the console conf with the correct name TODO:(address/port)
           2.: we have a console entry for this named console with the correct name and dirname 
               AND do we have a director with this dirname defined inside of this particular console conf
       
        
        Console view        
       B: iterate over all consoles,
       I   check if we have a director for every defined director in console (check names)
       II  check if our console definitions refer to defined Director resources inside of this consoleconfig (If we have consoles)
       III check if we have a named console definition in dirconf of director DIRNAME with the Name of our console conf and the same password
       IV  check for not by console entries referred directors if the console Director Passwd is identical to the Director default password   
      
      '''
                 
      '''
      TODO: implement the checks and output 
      TODO: implement test cases for this function!
      '''
   
       
  
  def safeDatacenter(self):
    '''
    safe the Datacenter under the known filename
    '''
    self.writeDatacenter(os.path.dirname(self.filename))
    
  def writeDatacenter(self,targetdir):
    '''
    create the subdirs 
    write the configs
    create tarfile
    '''
    tar = tarfile.open(targetdir + '/' + self.name + '.dmdz','w:gz')
    writedir = tempfile.mkdtemp(prefix = 'dassmodus-')
    writebasedir = writedir+'/' + self.name
    self.__createDataCenterDirs__(writebasedir)
   
    for director in self.directors:
      #print director.getName()
      writefile = open(writebasedir + '/' + 'directors' + '/' + director.getName() + '.conf','w')
      writefile.write(str(director))
      writefile.close()
      
    for storagedaemon in self.storagedaemons:
      #print storagedaemon.getName()
      writefile = open(writebasedir + '/' + 'storagedaemons' + '/' + storagedaemon.getName() + '.conf','w')
      writefile.write(str(storagedaemon))
      writefile.close()
      
    for filedaemon in self.filedaemons:
      #print storagedaemon.getName()
      writefile = open(writebasedir + '/' + 'filedaemons' + '/' + filedaemon.getName() + '.conf','w')
      writefile.write(str(filedaemon))
      writefile.close()
    
    consolenr = 0
    for console in self.consoles:
      #print storagedaemon.getName()
      name = console.getName()
      if name == '':
        consolenr += 1
        name = "bconsole-%d" % ( consolenr ) 
      writefile = open(writebasedir + '/' + 'consoles' + '/' + name + '.conf','w')
      writefile.write(str(console))
      writefile.close()
      
    
    #os.chdir(writedir)
    
    dirs = glob.glob(os.path.normpath(writedir + '/*/*'))
    for d in dirs:
      tar.add(d, arcname = d.replace(writedir+'/',''))
    tar.close()
    
    #  remove temp dir
    shutil.rmtree(writedir)
    
    
    


if __name__ == "__main__":
  #msgsstore = store_msgs("root@localhost = mount")
  import logging
  logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)s \t (%(module)s:%(lineno)d) %(message)s ',
                    #filename='vanHelsing.log',
                    filemode='w')
  cfg = '''
  Pool {
  LabelFormat = "${Year}-${Month:p/2/0/r}-${Day:p/2/0/r}"
}
'''
  dconfig = DirectorConfig(cfg)
