##############################################################################
#
# 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.ERP5Type import _dtmldir
from Products.ERP5Type.Cache import * 
from Products.ERP5Type.CachePlugins.RamCache import RamCache
from Products.ERP5Type.CachePlugins.DistributedRamCache import DistributedRamCache
from Products.ERP5Type.CachePlugins.SQLCache import SQLCache

## try to import needed modules for cache plugins
try:
  import memcache
except ImportError:
  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():
        cache_obj = None
        cp_meta_type = cp.meta_type
        if cp_meta_type == 'ERP5 Ram Cache':
          cache_obj = RamCache()
        elif cp_meta_type == 'ERP5 Distributed Ram Cache':
            ## even thougn we have such plugin in ZODB that doens't mean
            ## we have corresponding memcache module installed
          if 'memcache' in globals().keys():
            cache_obj = DistributedRamCache({'server':cp.getServer()})
          else:
            ## we don't have memcache python module installed 
            ## thus we can't use DistributedRamCache plugin
            cache_obj = None
        elif cp_meta_type == 'ERP5 SQL Cache':
          ## 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)
        if cache_obj:
          ## 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() 
    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 is not None:
      self.REQUEST.RESPONSE.redirect('cache_tool_configure?manage_tabs_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 """
    return CachingMethod.factories

  security.declareProtected(Permissions.ModifyPortalContent, 'updateCache')
  def updateCache(self, REQUEST=None):
    """ Clear and update cache structure """
    for cf in CachingMethod.factories:
      for cp in CachingMethod.factories[cf].getCachePluginList():
        del cp
    CachingMethod.factories = {}
    ## read configuration from ZODB
    for key,item in self.getCacheFactoryList().items():
      if len(item['cache_plugins'])!=0:
        ## init cache backend storages
        for cp in item["cache_plugins"]:
          cp.initCacheStorage()
        CachingMethod.factories[key] = CacheFactory(item['cache_plugins'], item['cache_params'])    
    if REQUEST is not None:
      self.REQUEST.RESPONSE.redirect('cache_tool_configure?manage_tabs_message=Cache updated.')
    
  security.declareProtected(Permissions.ModifyPortalContent, 'clearCache')
  def clearCache(self, cache_factory_list=(DEFAULT_CACHE_FACTORY,), REQUEST=None):
    """ Clear cache factory. """
    ram_cache_root = self.getRamCacheRoot()
    for cf_key in cache_factory_list:
      if ram_cache_root.has_key(cf_key):	    
        for cp in ram_cache_root[cf_key].getCachePluginList():
          cp.clearCache()
    if REQUEST is not None:
      self.REQUEST.RESPONSE.redirect('cache_tool_configure?manage_tabs_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 is not None:
      self.REQUEST.RESPONSE.redirect('cache_tool_configure?manage_tabs_message=Cache factory %s cleared.' %cache_factory_id)
    
  security.declareProtected(Permissions.ModifyPortalContent, 'clearCacheFactoryScope')
  def clearCacheFactoryScope(self, cache_factory_id, scope, 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].clearCacheForScope(scope)
    if REQUEST is not None:
      self.REQUEST.RESPONSE.redirect('cache_tool_configure?manage_tabs_message=Cache factory scope %s cleared.' %cache_factory_id)
  
