##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
#                     Ivan Tyagov <ivan@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################


""" Cache Tool module for ERP5 """
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type import Permissions
from Globals import InitializeClass, DTMLFile, PersistentMapping
from Products.ERP5Cache import _dtmldir
from Products.ERP5Type.Cache import CachingMethod, CacheFactory
from Products.ERP5Cache.CachePlugins.RamCache import RamCache
from Products.ERP5Cache.CachePlugins.DistributedRamCache import DistributedRamCache
from Products.ERP5Cache.CachePlugins.SQLCache import SQLCache

##try:
##  from Products.TimerService import getTimerService
##except ImportError:
##  def getTimerService(self):
##    pass


class CacheTool(BaseTool):
  """ Caches tool wrapper for ERP5 """
    
  id = "portal_caches"
  meta_type = "ERP5 Cache Tool"
  portal_type = "Cache Tool"

  security = ClassSecurityInfo()
  manage_options = ({'label': 'Configure',
                                'action': 'cache_tool_configure',
                    },) + BaseTool.manage_options

  security.declareProtected( Permissions.ManagePortal, 'cache_tool_configure')
  cache_tool_configure = DTMLFile( 'cache_tool_configure', _dtmldir )
  
  def __init__(self):
    BaseTool.__init__(self)

  security.declareProtected(Permissions.AccessContentsInformation, 'getCacheFactoryList')
  def getCacheFactoryList(self):
    """ Return available cache factories """
    rd ={}
    for cf in self.objectValues('ERP5 Cache Factory'):
      cache_scope = cf.getId()
      rd[cache_scope] = {}
      rd[cache_scope]['cache_plugins'] = []
      rd[cache_scope]['cache_params'] = {}
      for cp in cf.getCachePluginList():
        cp_meta_type = cp.meta_type
        if cp_meta_type == 'ERP5 Ram Cache Plugin':
          cache_obj = RamCache()
        elif cp_meta_type == 'ERP5 Distributed Ram Cache Plugin':
          cache_obj = DistributedRamCache({'server':cp.getServer()})
        elif cp_meta_type == 'ERP5 SQL Cache Plugin':
          ## use connection details from 'erp5_sql_transactionless_connection' ZMySLQDA object
          connection_string = self.erp5_sql_transactionless_connection.connection_string
          kw = self.parseDBConnectionString(connection_string)
          kw['cache_table_name'] = cp.getCacheTableName()
          cache_obj = SQLCache(kw)
        ## set cache expire check interval
        cache_obj.cache_expire_check_interval = cp.getCacheExpireCheckInterval() 
        rd[cache_scope]['cache_plugins'].append(cache_obj)
        rd[cache_scope]['cache_params']['cache_duration'] = cf.getCacheDuration() #getattr(cf, 'cache_duration', None)
    return rd

  ##
  ## DB structure
  ##
  security.declareProtected(Permissions.ModifyPortalContent, 'createDBCacheTable')
  def createDBCacheTable(self, cache_table_name="cache", REQUEST=None):
    """ create in MySQL DB cache table """
    my_query = SQLCache.create_table_sql %cache_table_name
    try:
      self.erp5_sql_transactionless_connection.manage_test("DROP TABLE %s" %cache_table_name)
    except:
      pass
    self.erp5_sql_transactionless_connection.manage_test(my_query)
    if REQUEST:
      self.REQUEST.RESPONSE.redirect('cache_tool_configure?portal_status_message=Cache table successfully created.')

  security.declareProtected(Permissions.AccessContentsInformation, 'parseDBConnectionString')
  def parseDBConnectionString(self, connection_string):
    """ Parse given connection string. Code "borrowed" from ZMySLQDA.db """
    kwargs = {}
    items = connection_string.split()
    if not items: 
      return kwargs
    lockreq, items = items[0], items[1:]
    if lockreq[0] == "*":
      db_host, items = items[0], items[1:]
    else:
      db_host = lockreq
    if '@' in db_host:
      db, host = split(db_host,'@',1)
      kwargs['db'] = db
      if ':' in host:
        host, port = split(host,':',1)
        kwargs['port'] = int(port)
      kwargs['host'] = host
    else:
      kwargs['db'] = db_host
    if kwargs['db'] and kwargs['db'][0] in ('+', '-'):
      kwargs['db'] = kwargs['db'][1:]
    if not kwargs['db']:
      del kwargs['db']
    if not items: 
      return kwargs
    kwargs['user'], items = items[0], items[1:]
    if not items: 
      return kwargs
    kwargs['passwd'], items = items[0], items[1:]
    if not items: 
      return kwargs
    kwargs['unix_socket'], items = items[0], items[1:]
    return kwargs
    
  ##
  ## RAM cache structure
  ##
  security.declareProtected(Permissions.AccessContentsInformation, 'getRamCacheRoot')
  def getRamCacheRoot(self):
    """ Return RAM based cache root """
    erp5_site_id = self.getPortalObject().getId()
    return CachingMethod.factories[erp5_site_id]

  security.declareProtected(Permissions.ModifyPortalContent, 'updateCache')
  def updateCache(self, REQUEST=None):
    """ Clear and update cache structure """
    erp5_site_id = self.getPortalObject().getId()
    for cf in CachingMethod.factories[erp5_site_id]:
      for cp in  CachingMethod.factories[erp5_site_id][cf].getCachePluginList():
        del cp
    CachingMethod.factories[erp5_site_id] = {}
    ## read configuration from ZODB
    for key,item in self.getCacheFactoryList().items():
      if len(item['cache_plugins'])!=0:
        CachingMethod.factories[erp5_site_id][key] = CacheFactory(item['cache_plugins'], item['cache_params'])    
    if REQUEST:
      self.REQUEST.RESPONSE.redirect('cache_tool_configure?portal_status_message=Cache updated.')
    
  security.declareProtected(Permissions.ModifyPortalContent, 'clearCache')
  def clearCache(self, REQUEST=None):
    """ Clear whole cache structure """
    ram_cache_root = self.getRamCacheRoot()
    for cf in ram_cache_root:
      for cp in ram_cache_root[cf].getCachePluginList():
        cp.clearCache()
    if REQUEST:
      self.REQUEST.RESPONSE.redirect('cache_tool_configure?portal_status_message=Cache cleared.')

  security.declareProtected(Permissions.ModifyPortalContent, 'clearCacheFactory')
  def clearCacheFactory(self, cache_factory_id, REQUEST=None):
    """ Clear only cache factory. """
    ram_cache_root = self.getRamCacheRoot()
    if ram_cache_root.has_key(cache_factory_id):
      ram_cache_root[cache_factory_id].clearCache()
    if REQUEST:
      self.REQUEST.RESPONSE.redirect('cache_tool_configure?portal_status_message=Cache factory %s cleared.' %cache_factory_id)

  
  # Timer - checks for cache expiration triggered by Zope's TimerService
##  def isSubscribed(self):
##      """
##      return True, if we are subscribed to TimerService.
##      Otherwise return False.
##      """
##      service = getTimerService(self)
##      if not service:
##          LOG('AlarmTool', INFO, 'TimerService not available')
##          return False
##
##      path = '/'.join(self.getPhysicalPath())
##      if path in service.lisSubscriptions():
##          return True
##      return False
##
##  security.declareProtected(Permissions.ManageProperties, 'subscribe')
##  def subscribe(self):
##    """
##      Subscribe to the global Timer Service.
##    """
##    service = getTimerService(self)
##    if not service:
##      LOG('AlarmTool', INFO, 'TimerService not available')
##      return
##    service.subscribe(self)
##    return "Subscribed to Timer Service"
##
##  security.declareProtected(Permissions.ManageProperties, 'unsubscribe')
##  def unsubscribe(self):
##    """
##      Unsubscribe from the global Timer Service.
##    """
##    service = getTimerService(self)
##    if not service:
##      LOG('AlarmTool', INFO, 'TimerService not available')
##      return
##    service.unsubscribe(self)
##    return "Usubscribed from Timer Service"
##
##  def manage_beforeDelete(self, item, container):
##    self.unsubscribe()
##    BaseTool.inheritedAttribute('manage_beforeDelete')(self, item, container)
##
##  def manage_afterAdd(self, item, container):
##    self.subscribe()
##    BaseTool.inheritedAttribute('manage_afterAdd')(self, item, container)
##
##  security.declarePrivate('process_timer')
##  def process_timer(self, interval, tick, prev="", next=""):
##    """
##      This method is called by TimerService in the interval given
##      in zope.conf. The Default is every 5 seconds. This method will
##      try to expire cache entries.
##    """
##    ram_cache_root = self.getRamCacheRoot()
##    for cf_id, cf_obj in ram_cache_root.items():
##      cf_obj.expire()