import os
from StringIO import StringIO
from App.version_txt import getZopeVersion
from Globals import package_home
from OFS.ObjectManager import BadRequestException

from Products.Archetypes.public import listTypes
from Products.Archetypes.Extensions.utils import installTypes
from Products.CMFCore.utils import getToolByName, minimalpath
from Products.CMFCore.DirectoryView import addDirectoryViews, \
     registerDirectory, manage_listAvailableDirectories
from Products.CMFCore import CachingPolicyManager
from Products.CacheSetup.config import PROJECT_NAME, GLOBALS, \
     CACHE_TOOL_ID, PAGE_CACHE_MANAGER_ID, OFS_CACHE_ID, RR_CACHE_ID

try:
    from Products.PageCacheManager import PageCacheManager
except ImportError:
    PageCacheManager = None
try:
    from Products.CMFSquidTool import SquidTool
except ImportError:
    SquidTool = None
try:
    from Products.PolicyHTTPCacheManager import PolicyHTTPCacheManager
except ImportError:
    PolicyHTTPCacheManager = None
from Products.StandardCacheManagers import RAMCacheManager, AcceleratedHTTPCacheManager
import Products.CacheSetup.utils
from Products.CacheSetup.utils import base_hasattr

TYPES = ('CacheTool','ContentCacheRule','TemplateCacheRule','PolicyHTTPCacheManagerCacheRule','RuleFolder','HeaderSet','HeaderSetFolder','MacroCacheRule','MacroFolder')
TOOL_TITLE = 'Cache Configuration Tool'
CONFIGLET_ID = 'CacheSetupPrefs'
CPM_ID = 'caching_policy_manager'

def setupConfiglet(portal, out):
    # see if there's already a cache configlet and try to
    # delete it
    
    portalConf = None
    try:
        portalConf = getToolByName(portal, 'portal_controlpanel')
    except AttributeError:
        print >>out, "Configlet could not be installed"
        return
    
    portalConf.unregisterConfiglet(CONFIGLET_ID)

    try:
        portalConf.registerConfiglet(
            CONFIGLET_ID,
            TOOL_TITLE,
            'string:${portal_url}/%s/cache_setup_config' % CACHE_TOOL_ID,
            '',                 # a condition
            'Manage portal',    # access permission
            'Products',         # section to which the configlet should be added:
                                #(Plone,Products,Members)
            1,                  # visibility
            PROJECT_NAME,
            '/misc_/CacheSetup/cachesetup_tool_icon.gif', # icon in control_panel
            TOOL_TITLE,
            None
        )
    except KeyError:
        pass # Get KeyError when registering duplicate configlet.

def removeConfiglet(portal, out):
    # remove configlet from portal_controlpanel
    pcp = getToolByName(portal,'portal_controlpanel')
    pcp.unregisterConfiglet(CONFIGLET_ID)

def installDependencies(portal, out):
    if PageCacheManager is None:
        raise ValueError, 'Please add PageCacheManager to your Products directory'
    id = PAGE_CACHE_MANAGER_ID
    if not id in portal.objectIds():
        PageCacheManager.manage_addPageCacheManager(portal, id)
        pcm = getattr(portal, id)
        pcm.setTitle('Page Cache Manager')
        print >> out, 'Added PageCacheManager %s' % id
    if SquidTool is None:
        raise ValueError, 'Please add CMFSquidTool to your Products directory'
    qi = portal.portal_quickinstaller
    if not 'portal_squid' in portal.objectIds():
        qi.installProduct('CMFSquidTool')
        print >> out, 'Installed CMFSquidTool'
    if PolicyHTTPCacheManager is None:
        raise ValueError, 'Please add PolicyHTTPCacheManager to your Products directory'

# move all skins to the front of the skin path
def installSubskin(self, out, globals=GLOBALS, product_skins_dir='skins'):
    skinstool=getToolByName(self, 'portal_skins')

    fullProductSkinsPath = os.path.join(package_home(globals), product_skins_dir)
    productSkinsPath = minimalpath(fullProductSkinsPath)
    registered_directories = manage_listAvailableDirectories()
    if productSkinsPath not in registered_directories:
        try:
            registerDirectory(product_skins_dir, globals)
        except OSError, ex:
            if ex.errno == 2: # No such file or directory
                return
            raise
    try:
        addDirectoryViews(skinstool, product_skins_dir, globals)
    except BadRequestException, e:
        pass  # directory view has already been added

    # see if we are running zope 2.8+
    zopeVersion = getZopeVersion()
    zope28plus = zopeVersion[0] == 2 and zopeVersion[1] >= 8
    # see if we have plone 2.1+

    ivt = getattr(self.portal_migration, 'getInstanceVersionTuple', None)
    if ivt is None:
        ploneVersion = (2, 0)
    else:
        ploneVersion = ivt()
    plone21 = ploneVersion[0] == 2 and ploneVersion[1] == 1
    
    files = os.listdir(fullProductSkinsPath)
    for productSkinName in files:
        if (os.path.isdir(os.path.join(fullProductSkinsPath, productSkinName))
            and productSkinName != 'CVS'
            and productSkinName != '.svn'):
            for skinName in skinstool.getSkinSelections():
                path = skinstool.getSkinPath(skinName)
                path = [i.strip() for i in  path.split(',')]
                try:
                    custom_index = path.index('custom')
                except ValueError:
                    custom_index = -1
                if productSkinName in path:
                    path.remove(productSkinName)
                # only use cache_setup_globals for Plone 2.1, Zope 2.8+ and
                # only use cache_prefs_plone20 for Plone 2.0
                if (productSkinName != 'cache_setup_globals' and productSkinName != 'cache_prefs_plone20') or \
                   (productSkinName == 'cache_setup_globals' and zope28plus and plone21) or \
                   (productSkinName == 'cache_prefs_plone20' and not plone21):
                    path.insert(custom_index+1, productSkinName)
                skinstool.addSkinSelection(skinName, ','.join(path))

def setupWorkflows(portal, out):
    """Setup workflow 
    """
    wftool = getToolByName(portal, 'portal_workflow')
    typeInfo = listTypes(PROJECT_NAME)
    for t in typeInfo:
        portal_type = t['portal_type']
        #if portal_type in ['HeaderSet']:
        #    wftool.setChainForPortalTypes((portal_type,), '')
        wftool.setChainForPortalTypes((portal_type,), '')

    # update workflow settings
    count = wftool.updateRoleMappings()

def setupPolicyHTTPCaches(portal, out):
    meta_type = PolicyHTTPCacheManager.PolicyHTTPCacheManager.meta_type
    for cache_id in ('HTTPCache', OFS_CACHE_ID):
        if cache_id not in portal.objectIds(spec=meta_type):
            if cache_id in portal.objectIds():
                portal.manage_delObjects([cache_id])
            PolicyHTTPCacheManager.manage_addPolicyHTTPCacheManager(portal, cache_id)
            print >> out, 'Installed PolicyHTTPCacheManager %s' % cache_id
            
def removePolicyHTTPCaches(portal, out):
    for obj in ['HTTPCache', OFS_CACHE_ID]:
        if hasattr(portal, obj):
            portal.manage_delObjects([obj])
    AcceleratedHTTPCacheManager.manage_addAcceleratedHTTPCacheManager(portal, 'HTTPCache')
    print >> out, 'Restored HTTPCache'

def setupResourceRegistry(portal,out):
    ram_cache_id = RR_CACHE_ID
    if not ram_cache_id in portal.objectIds():
        RAMCacheManager.manage_addRAMCacheManager(portal, ram_cache_id)
        cache = getattr(portal, ram_cache_id)
        settings = cache.getSettings()
        settings['max_age'] = 24*3600 # keep for up to 24 hours
        settings['request_vars'] = ('URL',)
        cache.manage_editProps('Cache for saved ResourceRegistry files', settings)
        print >> out, 'Created RAMCache %s for ResourceRegistry output' % ram_cache_id
    reg = getToolByName(portal, 'portal_css', None)
    if reg is not None and base_hasattr(reg, 'ZCacheable_setManagerId'):
        reg.ZCacheable_setManagerId(ram_cache_id)
        reg.ZCacheable_setEnabled(1)
        print >> out, 'Associated portal_css with %s' % ram_cache_id
    reg = getToolByName(portal, 'portal_javascripts', None)
    if reg is not None and base_hasattr(reg, 'ZCacheable_setManagerId'):
        reg.ZCacheable_setManagerId(ram_cache_id)
        reg.ZCacheable_setEnabled(1)
        print >> out, 'Associated portal_javascripts with %s' % ram_cache_id

def restoreResourceRegistry(portal, out):
    reg = getToolByName(portal, 'portal_css', None)
    if reg is not None and base_hasattr(reg, 'ZCacheable_setManagerId'):
        reg.ZCacheable_setManagerId(None)
        reg.ZCacheable_setEnabled(0)
        print >> out, 'Disabled portal_css caching'
    reg = getToolByName(portal, 'portal_javascripts', None)
    if reg is not None and base_hasattr(reg, 'ZCacheable_setManagerId'):
        reg.ZCacheable_setManagerId(None)
        reg.ZCacheable_setEnabled(0)
        print >> out, 'Disabled portal_javascripts caching'

def setupCookieCrumbler(portal, out):
    # prevent cookie crumbler from setting 'Cache-Control: private' on
    # all content
    pcs = getattr(portal, CACHE_TOOL_ID)
    cc = getattr(portal, 'cookie_authentication', None)
    if cc is not None:
        pcs.cookie_crumbler_header = getattr(cc,'cache_header_value',None)
        cc.cache_header_value = ''
        print >> out, 'Set cookie crumbler\'s cache_header_value to \'\''

def restoreCookieCrumbler(portal, out):
    pcs = getattr(portal, CACHE_TOOL_ID, None)
    if not pcs:
        return
    cc = getattr(portal, 'cookie_authentication', None)
    if cc is not None:
        cc.cache_header_value = pcs.cookie_crumbler_header
        print >> out, 'Restored cookie crumbler\'s cache_header_value'
    
def addHeaderSets(portal, out):
    cache_tool = getattr(portal, CACHE_TOOL_ID)
    header_sets = cache_tool.getHeaderSets()

    # migrate old header sets
    for h in header_sets.objectValues():
        if getattr(h, 'pageCache', None) is None:
            h.setPageCache(False)

    id = 'no-cache'
    if not id in header_sets.objectIds():
        header_sets.invokeFactory(id=id, type_name='HeaderSet')
        hs = getattr(header_sets, id)
        hs.setTitle('Do not cache')
        hs.setDescription('Page should not be cached.')
        hs.setPageCache(0)
        hs.setLastModified('yes')
        hs.setEtag(0)
        hs.setEnable304s(0)
        hs.setVary(True)
        hs.setMaxAge(0)
        hs.setSMaxAge(0)
        hs.setMustRevalidate(1)
        hs.setProxyRevalidate(0)
        hs.setNoCache(1)
        hs.setNoStore(0)
        hs.setPublic(0)
        hs.setPrivate(1)
        hs.setNoTransform(0)
        hs.setPreCheck(None)
        hs.setPostCheck(None)
        hs.reindexObject()

    id = 'cache-in-memory'
    if not id in header_sets.objectIds():
        header_sets.invokeFactory(id=id, type_name='HeaderSet')
        hs = getattr(header_sets, id)
        hs.setTitle('Cache in Memory')
        hs.setDescription('Page should be cached in memory on the server.  Page should not be cached in a proxy cache but may be conditionally cached in the browser.  The browser should validate the page\'s ETag before displaying a cached page.')
        hs.setPageCache(1)
        hs.setLastModified('delete')
        hs.setEtag(1)
        hs.setEnable304s(1)
        hs.setVary(True)
        hs.setMaxAge(0)
        hs.setSMaxAge(0)
        hs.setMustRevalidate(1)
        hs.setProxyRevalidate(0)
        hs.setNoCache(0)
        hs.setNoStore(0)
        hs.setPublic(0)
        hs.setPrivate(1)
        hs.setNoTransform(0)
        hs.setPreCheck(None)
        hs.setPostCheck(None)
        hs.reindexObject()

    id = 'cache-with-etag'
    if not id in header_sets.objectIds():
        header_sets.invokeFactory(id=id, type_name='HeaderSet')
        hs = getattr(header_sets, id)
        hs.setTitle('Cache with ETag')
        hs.setDescription('Page should not be cached in a proxy cache but may be conditionally cached in the browser.  The browser should validate the page\'s ETag before displaying a cached page.')
        hs.setPageCache(0)
        hs.setLastModified('delete')
        hs.setEtag(1)
        hs.setEnable304s(1)
        hs.setVary(True)
        hs.setMaxAge(0)
        hs.setSMaxAge(0)
        hs.setMustRevalidate(1)
        hs.setProxyRevalidate(0)
        hs.setNoCache(0)
        hs.setNoStore(0)
        hs.setPublic(0)
        hs.setPrivate(1)
        hs.setNoTransform(0)
        hs.setPreCheck(None)
        hs.setPostCheck(None)
        hs.reindexObject()

    id = 'cache-with-last-modified'
    if not id in header_sets.objectIds():
        header_sets.invokeFactory(id=id, type_name='HeaderSet')
        hs = getattr(header_sets, id)
        hs.setTitle('Cache file with Last-Modified')
        hs.setDescription('File should not be cached in a proxy cache but may be conditionally cached in the browser.  The browser should validate the file\'s Last-Modified time before displaying a cached file.')
        hs.setPageCache(0)
        hs.setLastModified('yes')
        hs.setEtag(0)
        hs.setEnable304s(1)
        hs.setVary(True)
        hs.setMaxAge(0)
        hs.setSMaxAge(0)
        hs.setMustRevalidate(1)
        hs.setProxyRevalidate(0)
        hs.setNoCache(0)
        hs.setNoStore(0)
        hs.setPublic(0)
        hs.setPrivate(1)
        hs.setNoTransform(0)
        hs.setPreCheck(None)
        hs.setPostCheck(None)
        hs.reindexObject()

    id = 'cache-in-proxy-1-hour'
    if not id in header_sets.objectIds():
        header_sets.invokeFactory(id=id, type_name='HeaderSet')
        hs = getattr(header_sets, id)
        hs.setTitle('Cache in proxy cache for 1 hour')
        hs.setDescription('Cache the page in the proxy cache for up to 1 hour.  If the template is associated with PageCacheManager, the page may be cached in memory on the server.')
        hs.setPageCache(0)
        hs.setLastModified('yes')
        hs.setEtag(0)
        hs.setEnable304s(0)
        hs.setVary(True)
        hs.setMaxAge(0)
        hs.setSMaxAge(3600)
        hs.setMustRevalidate(1)
        hs.setProxyRevalidate(0)
        hs.setNoCache(0)
        hs.setNoStore(0)
        hs.setPublic(0)
        hs.setPrivate(0)
        hs.setNoTransform(0)
        hs.setPreCheck(None)
        hs.setPostCheck(None)
        hs.reindexObject()

    id = 'cache-in-proxy-24-hours'
    if not id in header_sets.objectIds():
        header_sets.invokeFactory(id=id, type_name='HeaderSet')
        hs = getattr(header_sets, id)
        hs.setTitle('Cache in proxy cache for 24 hours')
        hs.setDescription('Cache the page in the proxy cache for up to 24 hours.  If the template is associated with PageCacheManager, the page may be cached in memory on the server.')
        hs.setPageCache(0)
        hs.setLastModified('yes')
        hs.setEtag(0)
        hs.setEnable304s(0)
        hs.setVary(True)
        hs.setMaxAge(0)
        hs.setSMaxAge(24*3600)
        hs.setMustRevalidate(1)
        hs.setProxyRevalidate(1)
        hs.setNoCache(0)
        hs.setNoStore(0)
        hs.setPublic(0)
        hs.setPrivate(0)
        hs.setNoTransform(0)
        hs.setPreCheck(None)
        hs.setPostCheck(None)
        hs.reindexObject()

    id = 'cache-in-browser-1-hour'
    if not id in header_sets.objectIds():
        header_sets.invokeFactory(id=id, type_name='HeaderSet')
        hs = getattr(header_sets, id)
        hs.setTitle('Cache in browser for 1 hour')
        hs.setDescription('Cache the page in the client\'s browser for 1 hour.  Also caches in the proxy for up to 1 hour, and if the template is associated with PageCacheManager, the page may be cached in memory on the server.')
        hs.setPageCache(0)
        hs.setLastModified('yes')
        hs.setEtag(0)
        hs.setEnable304s(0)
        hs.setVary(True)
        hs.setMaxAge(3600)
        hs.setSMaxAge(3600)
        hs.setMustRevalidate(1)
        hs.setProxyRevalidate(1)
        hs.setNoCache(0)
        hs.setNoStore(0)
        hs.setPublic(1)
        hs.setPrivate(0)
        hs.setNoTransform(0)
        hs.setPreCheck(None)
        hs.setPostCheck(None)
        hs.reindexObject()

    id = 'cache-in-browser-24-hours'
    if not id in header_sets.objectIds():
        header_sets.invokeFactory(id=id, type_name='HeaderSet')
        hs = getattr(header_sets, id)
        hs.setTitle('Cache in browser for 24 hours')
        hs.setDescription('Cache the page in the client\'s browser for 24 hours.  Also caches in the proxy for up to 24 hours, and if the template is associated with PageCacheManager, the page may be cached in memory on the server.')
        hs.setPageCache(0)
        hs.setLastModified('yes')
        hs.setEtag(0)
        hs.setEnable304s(0)
        hs.setVary(True)
        hs.setMaxAge(24*3600)
        hs.setSMaxAge(24*3600)
        hs.setMustRevalidate(1)
        hs.setProxyRevalidate(1)
        hs.setNoCache(0)
        hs.setNoStore(0)
        hs.setPublic(1)
        hs.setPrivate(0)
        hs.setNoTransform(0)
        hs.setPreCheck(None)
        hs.setPostCheck(None)
        hs.reindexObject()

    id = 'cache-in-browser-forever'
    if not id in header_sets.objectIds():
        header_sets.invokeFactory(id=id, type_name='HeaderSet')
        hs = getattr(header_sets, id)
        hs.setTitle('Cache in browser forever')
        hs.setDescription('Cache the file in the client\'s browser for 1 year.  Also caches in the proxy for up to 1 year.  No ETag is set so the file will not be cached in memory on the server.')
        hs.setPageCache(0)
        hs.setLastModified('yes')
        hs.setEtag(0)
        hs.setEnable304s(0)
        hs.setVary(True)
        hs.setMaxAge(365*24*3600)
        hs.setSMaxAge(365*24*3600)
        hs.setMustRevalidate(0)
        hs.setProxyRevalidate(0)
        hs.setNoCache(0)
        hs.setNoStore(0)
        hs.setPublic(1)
        hs.setPrivate(0)
        hs.setNoTransform(0)
        hs.setPreCheck(None)
        hs.setPostCheck(None)
        hs.reindexObject()

def setupCacheTool(portal, out):
    if not CACHE_TOOL_ID in portal.objectIds():
        ps = portal.portal_types.getTypeInfo('Plone Site')
        allowed_types = ps.getProperty('allowed_content_types')
        ps._updateProperty('allowed_content_types', tuple(list(allowed_types)+['CacheTool']))

        portal.invokeFactory(id=CACHE_TOOL_ID, type_name='CacheTool')
        cache_tool = getattr(portal, CACHE_TOOL_ID)
        cache_tool.setTitle(TOOL_TITLE)
        cache_tool.reindexObject()
        ps._updateProperty('allowed_content_types', allowed_types)
        print >> out, 'Added %s' % CACHE_TOOL_ID
    ct = getattr(portal, CACHE_TOOL_ID)
    ct.unindexObject()

def addCacheRules(portal, out):
    cache_tool = getattr(portal, CACHE_TOOL_ID)
    rules = cache_tool.getRules()

    id = 'httpcache'
    if id not in rules.objectIds():
        rules.invokeFactory(id=id, type_name='PolicyHTTPCacheManagerCacheRule')
        rule = getattr(rules, id)
        rule.setTitle('HTTPCache')
        rule.setCacheManager('HTTPCache')
        rule.setDescription('Rule for content associated with HTTPCache.  This content is cached in the proxy and in the browser.  ETags are not useful because these files have no personalization.')
        rule.setCacheStop([])
        rule.setLastModifiedExpression('python:object.modified()')
        rule.setHeaderSetIdAnon('cache-in-browser-24-hours')
        rule.setHeaderSetIdAuth('cache-in-browser-24-hours')
        rule.setVaryExpression('python:getattr(object, \'meta_type\', None) not in [\'Filesystem Image\', \'Image\'] and rule.portal_cache_settings.getVaryHeader() or \'\'')
        rule.reindexObject()
        
    id = 'plone-content-types'
    if id not in rules.objectIds():
        rules.invokeFactory(id=id, type_name='ContentCacheRule')
        rule = getattr(rules, id)
        rule.setTitle('Content')
        rule.setDescription('Rule for views of plone content types.  Anonymous users are served content object views from the proxy cache.  These views are purged when content objects change.  Authenticated users are served pages from memory.  Member ID is used in the ETag because content is personalized; the time of the last catalog change is included so that the navigation tree stays up to date.')
        rule.setContentTypes(['Document', 'Event', 'Link', 'News Item', 'Image', 'File'])
        rule.setDefaultView(True)
        rule.setTemplates([])
        rule.setCacheStop(['portal_status_message', 'statusmessages',
                           'SearchableText'])
        rule.setLastModifiedExpression('python:object.modified()')
        rule.setHeaderSetIdAnon('cache-in-memory')
        rule.setHeaderSetIdAuth('cache-with-etag')
        rule.setEtagComponents(['member','catalog_modified','language','gzip','skin'])
        rule.setEtagRequestValues(['month','year','orig_query'])
        rule.setEtagTimeout(3600)
        rule.setPurgeExpression('python:object.getImageAndFilePurgeUrls()')
        rule.reindexObject()
        
    id = 'plone-containers'
    if id not in rules.objectIds():
        rules.invokeFactory(id=id, type_name='ContentCacheRule')
        rule = getattr(rules, id)
        rule.setTitle('Containers')
        rule.setDescription('Rule for views of Plone containers.  Both anonymous and authenticated users are served pages from memory, not the proxy cache.  The reason is that we can\'t easily purge container views when they change since container views depend on all of their contained objects, and contained objects do not necessarily purge their containers\' views when they change.  Member ID is used in the ETag because content is personalized; the time of the last catalog change is included so that the contents and the navigation tree stays up to date.')
        rule.setContentTypes(['Topic', 'Folder', 'Plone Site', 'Large Plone Folder'])
        rule.setDefaultView(True)
        rule.setTemplates(['folder_contents', 'RSS'])
        rule.setCacheStop(['portal_status_message', 'statusmessages',
                           'SearchableText'])
        rule.setLastModifiedExpression('python:object.modified()')
        rule.setHeaderSetIdAnon('cache-in-memory')
        rule.setHeaderSetIdAuth('cache-with-etag')
        rule.setEtagComponents(['member','catalog_modified','language','gzip','skin'])
        rule.setEtagRequestValues(['b_start','month','year','orig_query'])
        rule.setEtagExpression('python:request.get(\'__cp\',None) is not None')
        rule.setEtagTimeout(3600)
        rule.reindexObject()
        
    id = 'plone-templates'
    if id not in rules.objectIds():
        rules.invokeFactory(id=id, type_name='TemplateCacheRule')
        rule = getattr(rules, id)
        rule.setTitle('Templates')
        rule.setDescription('Rule for various non-form templates.  Both anonymous and authenticated users are served pages from memory, not the proxy cache, because some of these templates depend on catalog queries.  Member ID is used in the ETag because content is personalized; the time of the last catalog change is included so that the contents and the navigation tree stays up to date.')
        rule.setTemplates(['accessibility-info','sitemap','recently_modified'])
        rule.setCacheStop(['portal_status_message', 'statusmessages',
                           'SearchableText'])
        rule.setLastModifiedExpression('python:object.modified()')
        rule.setHeaderSetIdAnon('cache-in-memory')
        rule.setHeaderSetIdAuth('cache-with-etag')
        rule.setEtagComponents(['member','catalog_modified','language','gzip','skin'])
        rule.setEtagRequestValues(['month','year','orig_query'])
        rule.setEtagTimeout(3600)
        rule.reindexObject()
        
    id = 'resource-registries'
    if id not in rules.objectIds():
        rules.invokeFactory(id=id, type_name='PolicyHTTPCacheManagerCacheRule')
        rule = getattr(rules, id)
        rule.setTitle('CSS & JS')
        rule.setDescription('Rule for CSS and JS generated by ResourceRegistries.  These files are cached "forever" (1 year) in squid and in browsers.  There is no need to purge these files because when they are changed and saved in portal_css/portal_js, their file names change.  ETags are not useful because these files have no personalization.')
        rule.setCacheManager(OFS_CACHE_ID)
        rule.setCacheStop([])
        rule.setLastModifiedExpression('python:object.modified()')
        rule.setTypes(['File'])
        rule.setHeaderSetIdAnon('expression')
        rule.setHeaderSetIdAuth('expression')
        rule.setHeaderSetIdExpression('python:object.getHeaderSetIdForCssAndJs()')
        rule.setVaryExpression('string:')
        rule.reindexObject()

    id = 'downloads'
    if id not in rules.objectIds():
        rules.invokeFactory(id=id, type_name='PolicyHTTPCacheManagerCacheRule')
        rule = getattr(rules, id)
        rule.setTitle('Files & Images')
        rule.setDescription('Rule for ATFile and ATImage downloads.  Files that are viewable by Anonymous users are cached in squid; files that are not viewable by Anonymous users are cached only on the browser.  ETags are not useful because these files have no personalization.')
        rule.setCacheManager(OFS_CACHE_ID)
        rule.setTypes(['Image', 'File'])
        rule.setCacheStop([])
        rule.setLastModifiedExpression('python:object.modified()')
        rule.setHeaderSetIdAnon('expression')
        rule.setHeaderSetIdAuth('expression')
        rule.setHeaderSetIdExpression('python:object.portal_cache_settings.canAnonymousView(object) and \'cache-in-proxy-24-hours\' or \'no-cache\'')
        rule.setVaryExpression('string:')
        rule.reindexObject()

    id = 'dtml-css'
    if id not in rules.objectIds():
        rules.invokeFactory(id=id, type_name='TemplateCacheRule')
        rule = getattr(rules, id)
        rule.setTitle('DTML CSS files')
        rule.setDescription('Rule for css files generated with DTML.  These files will be cached in the browser for 24 hours.')
        rule.setTemplates(['IEFixes.css'])
        rule.setCacheStop([])
        rule.setLastModifiedExpression('python:object.modified()')
        rule.setHeaderSetIdAnon('cache-in-browser-24-hours')
        rule.setHeaderSetIdAuth('cache-in-browser-24-hours')
        rule.setEtagComponents([])
        rule.setEtagRequestValues([])
        rule.setEtagTimeout(None)
        rule.setVaryExpression('string:')
        rule.reindexObject()

def addMacros(portal, out):
    cache_tool = getattr(portal, CACHE_TOOL_ID)
    macros = cache_tool.getMacros()

    #id = 'm-html-header'
    #if not id in macros.objectIds():
    #    macros.invokeFactory(id=id, type_name='MacroCacheRule')
    #    macro = getattr(macros, id)
    #    macro.setTitle('Header')
    #    macro.setMacroName('file:CMFPlone/skins/plone_templates/header.pt/macros/html_header')
    #    macro.setKeyComponents(['url','roles','language'])
    #    macro.reindexObject()

    id = 'm-site-actions'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Site actions')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/global_siteactions.pt/macros/site_actions')
        macro.setKeyComponents(['host','roles','language'])
        macro.reindexObject()

    id = 'm-search'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Search box')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/global_searchbox.pt/macros/quick_search')
        macro.setKeyComponents(['host','language'])
        macro.reindexObject()

    id = 'm-logo'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Logo')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/global_logo.pt/macros/portal_logo')
        macro.setKeyComponents(['host','language'])
        macro.reindexObject()
        
    id = 'm-skin-switcher'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Skin switcher')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/global_skinswitcher.pt/macros/skin_tabs')
        macro.setKeyComponents(['host','roles','language'])
        macro.reindexObject()
        
    id = 'm-portal-tabs'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Portal tabs')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/global_sections.pt/macros/portal_tabs')
        macro.setKeyComponents(['host','context','template','roles','language'])
        macro.reindexObject()
        
    id = 'm-personal-bar'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Personal bar')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/global_personalbar.pt/macros/personal_bar')
        macro.setKeyComponents(['host','member', 'permissions','language'])
        macro.reindexObject()
        
    id = 'm-path-bar'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Path bar')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/global_pathbar.pt/macros/path_bar')
        macro.setKeyComponents(['host','context','language'])
        macro.reindexObject()
        
    id = 'm-content-views'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Content views')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/global_contentviews.pt/macros/content_views')
        macro.setKeyComponents(['host','context','last_modified','permissions','template','language'])
        macro.reindexObject()
        
    id = 'm-content-actions'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Content actions')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/global_contentviews.pt/macros/content_actions')
        macro.setKeyComponents(['host','context','last_modified','permissions','template','language'])
        macro.reindexObject()
        
    id = 'm-discussion'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Discussion')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/viewThreadsAtBottom.pt/macros/discussionView')
        macro.setKeyComponents(['host','context','roles','catalog_modified','language'])
        macro.reindexObject()
        
    id = 'm-footer'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Footer')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/footer.pt/macros/portal_footer')
        macro.setKeyComponents(['host','language'])
        macro.reindexObject()
        
    id = 'm-colophon'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Colophon')
        macro.setMacroName('file:CMFPlone/skins/plone_templates/colophon.pt/macros/colophon')
        macro.setKeyComponents(['host','language'])
        macro.reindexObject()

    # portlets
        
    id = 'm-events-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Events portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_events.pt/macros/portlet')
        macro.setKeyComponents(['host','roles', 'catalog_modified','language'])
        macro.reindexObject()
        
    id = 'm-news-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('News portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_news.pt/macros/portlet')
        macro.setKeyComponents(['host','roles', 'catalog_modified','language'])
        macro.reindexObject()
        
    id = 'm-recent-items-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Recent items portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_recent.pt/macros/portlet')
        macro.setKeyComponents(['host','roles', 'catalog_modified','language'])
        macro.reindexObject()
        
    id = 'm-review-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Review items portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_review.pt/macros/portlet')
        macro.setKeyComponents(['host','roles', 'catalog_modified','language'])
        macro.reindexObject()
        
    id = 'm-login-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Login portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_login.pt/macros/portlet')
        macro.setKeyComponents(['host','roles','language'])
        macro.reindexObject()
        
    id = 'm-prefs-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Preferences portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_prefs.pt/macros/portlet')
        macro.setKeyComponents(['host','roles','language'])
        macro.reindexObject()
        
    id = 'm-favorites-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Favorites portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_favorites.pt/macros/portlet')
        macro.setKeyComponents(['host','member', 'catalog_modified','language'])
        macro.reindexObject()
        
    id = 'm-related-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Related items portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_related.pt/macros/portlet')
        macro.setKeyComponents(['host','context', 'roles', 'catalog_modified','language'])
        macro.reindexObject()
        
    id = 'm-navigation-portlet-helper'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Navigation portlet helper macro')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_navtree_macro.pt/macros/nav_main')
        macro.setKeyComponents(['host','context', 'roles', 'catalog_modified','language'])
        macro.reindexObject()
        
    id = 'm-navigation-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Navigation portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_navigation.pt/macros/portlet')
        macro.setKeyComponents(['host','context', 'roles', 'catalog_modified','language'])
        macro.reindexObject()
        
    id = 'm-calendar-portlet'
    if not id in macros.objectIds():
        macros.invokeFactory(id=id, type_name='MacroCacheRule')
        macro = getattr(macros, id)
        macro.setTitle('Calendar portlet')
        macro.setMacroName('file:CMFPlone/skins/plone_portlets/portlet_calendar.pt/macros/portlet')
        macro.setKeyComponents(['host','roles', 'catalog_modified'])
        macro.setKeyRequestValues(['month', 'year', 'orig_query','language'])
        macro.reindexObject()        

def setupSiteProperties(portal, out):
    try:
        types_not_searched = list(portal.portal_properties.site_properties.types_not_searched)
    except AttributeError:
        types_not_searched = []
    for t in TYPES:
        if not t in types_not_searched:
            types_not_searched.append(t)
    use_folder_tabs = list(portal.portal_properties.site_properties.use_folder_tabs)
    if 'CacheTool' in use_folder_tabs:
        use_folder_tabs.remove('CacheTool')
    portal.portal_properties.site_properties.manage_changeProperties(types_not_searched=tuple(types_not_searched),
                                                                     use_folder_tabs=tuple(use_folder_tabs))
    metaTypesNotToList = list(portal.portal_properties.navtree_properties.metaTypesNotToList)
    if not 'CacheTool' in metaTypesNotToList:
        metaTypesNotToList.append('CacheTool')
    portal.portal_properties.navtree_properties.manage_changeProperties(metaTypesNotToList=tuple(metaTypesNotToList))

def restoreSiteProperties(portal, out):
    try:
        types_not_searched = list(portal.portal_properties.site_properties.types_not_searched)
    except AttributeError:
        types_not_searched = []
    for t in TYPES:
        if t in types_not_searched:
            types_not_searched.remove(t)
    use_folder_tabs = list(portal.portal_properties.site_properties.use_folder_tabs)
    portal.portal_properties.site_properties.manage_changeProperties(types_not_searched=tuple(types_not_searched))
    metaTypesNotToList = list(portal.portal_properties.navtree_properties.metaTypesNotToList)
    if 'CacheTool' in metaTypesNotToList:
        metaTypesNotToList.remove('CacheTool')
    portal.portal_properties.navtree_properties.manage_changeProperties(metaTypesNotToList=tuple(metaTypesNotToList))

def setupSquidTool(portal, out):
    squid_tool = portal.portal_squid
    squid_tool.setUrlExpression('python:object.%s.getUrlsToPurge(object)' % CACHE_TOOL_ID)

def restoreSquidTool(portal, out):
    # get rid of the url expression in portal_squid
    squid_tool = getattr(portal, 'portal_squid', None)
    if squid_tool:
        squid_tool.setUrlExpression('')

def setupPortalFactory(portal, out):
    factory = getToolByName(portal, 'portal_factory', None)
    if factory is not None:
        types = factory.getFactoryTypes().keys()
        for metaType in TYPES:
            if metaType not in types:
                types.append(metaType)
        factory.manage_setPortalFactoryTypes(listOfTypeIds = types)
        print >> out, 'Added content types to portal_factory.'
        
def restorePortalFactory(portal, out):
    factory = getToolByName(portal, 'portal_factory', None)
    if factory is not None:
        types = factory.getFactoryTypes().keys()
        for metaType in TYPES:
            if metaType in types:
                types.remove(metaType)
        factory.manage_setPortalFactoryTypes(listOfTypeIds = types)
        print >> out, 'Removed content types from portal_factory.'

def removeCachingPolicyManager(portal, out):
    cpm = getattr(portal, CPM_ID, None)
    if not cpm.__class__ == CachingPolicyManager.CachingPolicyManager:
        return
    
    old_policies = []
    if cpm:
        for id, p in cpm.listPolicies():
            # crude check for right version of CMF
            if getattr(p, 'getSMaxAgeSecs', None):
                old_policies.append(
                    {'policy_id': id,
                     'predicate': p.getPredicate(),
                     'mtime_func': p.getMTimeFunc(),
                     'max_age_secs': p.getMaxAgeSecs(),
                     'no_cache': p.getNoCache(),
                     'no_store': p.getNoStore(),
                     'must_revalidate': p.getMustRevalidate(),
                     'vary': p.getVary(),
                     'etag_func': p.getETagFunc(),
                     's_max_age_secs': p.getSMaxAgeSecs(),
                     'proxy_revalidate': p.getProxyRevalidate(),
                     'public': p.getPublic(),
                     'private': p.getPrivate(),
                     'no_transform': p.getNoTransform(),
                     'enable_304s': p.getEnable304s(),
                     'last_modified': p.getLastModified(),
                     'pre_check': p.getPreCheck(),
                     'post_check': p.getPostCheck(),
                     })
        portal.manage_delObjects([CPM_ID])
    pcs = getattr(portal, CACHE_TOOL_ID)
    pcs.old_policies = old_policies
    portal.manage_addProduct['CacheSetup'].manage_addTool('CacheFu Caching Policy Manager')
    print >> out, 'Replaced %s' % CPM_ID

def restoreCachingPolicyManager(portal, out):
    portal.manage_delObjects([CPM_ID])
    print >> out, 'Removed %s' % CPM_ID
    pcs = getattr(portal, CACHE_TOOL_ID, None)
    if pcs:
        old_policies = getattr(pcs, 'old_policies', [])
    else:
        old_policies = []
    CachingPolicyManager.manage_addCachingPolicyManager(portal)
    cpm = getattr(portal, CPM_ID)
    for p in old_policies:
        cpm.addPolicy(p['policy_id'], p['predicate'], p['mtime_func'], p['max_age_secs'],
                      p['no_cache'], p['no_store'], p['must_revalidate'], p['vary'],
                      p['etag_func'], None, p['s_max_age_secs'], p['proxy_revalidate'], 
                      p['public'], p['private'], p['no_transform'], p['enable_304s'],
                      p['last_modified'], p['pre_check'], p['post_check'])
    print >> out, 'Restored old caching policy manager'

def install(self, reinstall=False):
    out = StringIO()
    installTypes(self, out, listTypes(PROJECT_NAME), PROJECT_NAME)
    installSubskin(self, out, GLOBALS)
    installDependencies(self, out)

    setupWorkflows(self, out)
    setupCacheTool(self, out)
    addHeaderSets(self, out)
    addCacheRules(self, out)
    addMacros(self, out)

    removeCachingPolicyManager(self, out)
    setupCookieCrumbler(self, out)
    setupSquidTool(self, out)
    setupPolicyHTTPCaches(self, out)
    setupResourceRegistry(self, out)
    setupPortalFactory(self, out)
    setupSiteProperties(self, out)
    setupConfiglet(self, out)

    out.write("Successfully installed %s." % PROJECT_NAME)
    return out.getvalue()

def uninstall(self, reinstall=False):
    out = StringIO()

    # restore the old caching policy manager
    restoreCachingPolicyManager(self, out)
    # restore the cookie crumbler's settings
    restoreCookieCrumbler(self, out)
    # restore the resource registries to their uncached state
    restoreResourceRegistry(self, out)
    restorePortalFactory(self, out)
    removePolicyHTTPCaches(self, out)
    restoreSquidTool(self, out)
    restoreSiteProperties(self, out)
    removeConfiglet(self, out)

    cache_tool = getattr(self, CACHE_TOOL_ID)
    
    if not reinstall:
        print >> out, 'The standard cache settings may have changed since the last version.  If you have difficulties, try uninstalling then installing.'
        self.manage_delObjects([CACHE_TOOL_ID])

    return out.getvalue()
