Policies
========

Most of the operations done on Managed Files are governed by
policies. These policies are pluggable, by using a module-level
registries which are then exposed through the Managed File Tool.

We have policies for that govern filesystem structure (filesystem
policy), deletion (delete policy) and filename (filename policy).

  >>> from Products.CMFManagedFile import policies
  >>> policies.filesystem_policies
  <Registry with 4 items>

  >>> policies.delete_policies
  <Registry with 4 items>

  >>> policies.filename_policies
  <Registry with 2 items>

We have some policies registered by default:

  >>> from Products.CMFManagedFile import config
  >>> config.DELETE_MANUAL in policies.delete_policies.keys()
  True

  >>> config.DELETE_DEFERRED in policies.delete_policies.keys()
  True

  >>> config.DELETE_IMMEDIATE in policies.delete_policies.keys()
  True

  >>> config.TEMP_DOCUMENT in policies.delete_policies.keys()
  True

  >>> config.GENERATE_POLICY in policies.filename_policies.keys()
  True

  >>> config.ORIGINAL_POLICY in policies.filename_policies.keys()
  True

  >>> config.DELETE_IMMEDIATE in policies.delete_policies.keys()
  True

  >>> config.FS_BASE in policies.filesystem_policies.keys()
  True

  >>> config.FS_RELATIVE in policies.filesystem_policies.keys()
  True

  >>> config.FS_PORTAL_TYPE in policies.filesystem_policies.keys()
  True

Path Policies
=============

Path policies calculate the target path for a given object. They
require very little from the object, so we are going to fake a object
with some settings and see if the path is being calculated correctly.


  >>> from Acquisition import Implicit, aq_inner
  >>> class ManagedFile(Implicit):
  ...   def __init__(self, **kw):
  ...     self.__dict__.update(kw)
  ...   def getId(self):
  ...     return self.id
  ...   def getPhysicalPath(self):
  ...       path = aq_inner(self).aq_parent.getPhysicalPath()
  ...       return tuple(path) + (self.getId(),)

  >>> file = ManagedFile
  >>> from Products.CMFManagedFile.config import *
  >>> getComponent = policies.filesystem_policies.component

Base Path Policy
================

The Base Path Policy stores all files under the same directory. It may
be useful for some cases, but certainly not if you have objects with
the same id and use the ORIGINAL_POLICY for filename policy. It's
recommended that you use smarter path policies or at least change the
filename policy to GENERATE_POLICY.

  >>> base = getComponent(FS_BASE, context=self.tool, wrapped=True)
  >>> path = base.getPathFor

If a basedir is set but no repository, then we use the default
repository path + basedir:

  >>> from os.path import join
  >>> dp = config.DEFAULT_PATH
  >>> path(file(id='test', basedir='tmp')) == join(dp, 'tmp', 'test')
  True

Setup a repository pointing to 'foo'

  >>> repo = self.tool.addRepository('fi', path='foo',
  ...                                path_policy=FS_BASE,
  ...                                filename_policy=ORIGINAL_POLICY)

File has a repository set. Use the repository path + basedir +
filename:

  >>> f = file(id='test', repository='fi', basedir='bar')
  >>> expected = join('foo', 'bar', 'test')
  >>> path(f) == expected
  False

  >>> path(f).endswith(expected)
  True

If the file has a repository set, but the repository can't be found,
we raise a KeyError:

  >>> f = file(id='test', repository='fa', basedir='bar')
  >>> path(f)
  Traceback (most recent call last):
  ...
  KeyError: 'fa'

If the file has a repository set but the basedir is not set, we just
join the repository path and the filename:

  >>> f = file(id='test', repository='fi')
  >>> expected = join('foo', 'test')
  >>> path(f) == expected
  False

  >>> path(f).endswith(expected)
  True

If we force overriding the filename, it will forcibly use the filename
we asked for:

  >>> f = file(id='test', repository='fi')
  >>> expected = join('foo', 'fuba')
  >>> path(f, filenameOverride='fuba') == expected
  False

  >>> path(f, filenameOverride='fuba').endswith(expected)
  True

Create a custom filename policy:

  >>> import time
  >>> def date(filename):
  ...     return '.'.join((time.strftime('%Y%m%d', time.localtime()), filename))

  >>> TEST_DATE_POLICY = 'TestDatePolicy'
  >>> policies.filename_policies.register(date, name=TEST_DATE_POLICY)

Setup a repository pointing to 'bar', with a special filename policy:

  >>> repo = self.tool.addRepository('ba', path='bar',
  ...                                path_policy=FS_BASE,
  ...                                filename_policy=TEST_DATE_POLICY)

The filename should be computed using the policy:

  >>> filename = date('test')
  >>> f = file(id='test', repository='ba')
  >>> expected = join('bar', filename)
  >>> path(f) == expected
  False

  >>> path(f).endswith(expected)
  True

If no repository is found, it should use the default repository, and
the default policy for filenames which is ORIGINAL_POLICY:

  >>> f = file(id='test', basedir='bar')
  >>> path(f) == join(dp, 'bar', 'test')
  True

Change tool policy for filenames:

  >>> self.tool._updateProperty('filename_policy', TEST_DATE_POLICY)
  >>> filename = date('test')
  >>> path(f) == join(dp, 'bar', filename)
  True

Restore tool policy:

  >>> self.tool._updateProperty('filename_policy', ORIGINAL_POLICY)
  >>> path(f) == join(dp, 'bar', 'test')
  True

Relative Path Policy
====================

The Relative Path Policy stores files using the same relative path
under the repository as the relative path of the file inside the
portal.

  >>> rel = getComponent(FS_RELATIVE, context=self.tool, wrapped=True)
  >>> path = rel.getPathFor

  >>> def wrap(context, obj):
  ...     return obj.__of__(context)

  >>> os.path.exists(join(dp, 'Members', 'xxx_bar_xxx'))
  False

  >>> f = file(id='test', basedir='xxx_bar_xxx')
  >>> f = wrap(self.portal.Members, f)
  >>> path(f, createpath=True) == join(dp, 'Members', 'xxx_bar_xxx', 'test')
  True

  >>> os.path.exists(join(dp, 'Members', 'xxx_bar_xxx'))
  True

  >>> os.path.isdir(join(dp, 'Members', 'xxx_bar_xxx'))
  True

  >>> self.tool.cleanupFiles(join(dp, 'Members', 'xxx_bar_xxx', 'test'))

WARNING: The next test may fail if you are running the tests on the
production instance, but it's very unlikely:

  >>> os.path.exists(join(dp, 'Members', 'xxx_bar_xxx'))
  False


Physical Path Policy
====================

The Physical Path Policy stores files using the same physical path
under the repository as the relative path of the file inside the ZODB.

  >>> rel = getComponent(FS_PHYSICAL, context=self.tool, wrapped=True)
  >>> path = rel.getPathFor

  >>> def wrap(context, obj):
  ...     return obj.__of__(context)

  >>> base = join(dp, 'portal', 'Members', 'xxx_bar_xxx')
  >>> os.path.exists(base)
  False

  >>> f = file(id='test', basedir='xxx_bar_xxx')
  >>> f = wrap(self.portal.Members, f)
  >>> path(f, createpath=True) == join(base, 'test')
  True

  >>> os.path.exists(base)
  True

  >>> os.path.isdir(base)
  True

  >>> self.tool.cleanupFiles(join(base, 'test'))

WARNING: The next test may fail if you are running the tests on the
production instance, but it's very unlikely:

  >>> os.path.exists(base)
  False


Portal Type Policy
==================

The Portal Type Policy uses the object's portal type as the top-level
directory to organize the files by portal type:

  >>> class PortalManagedFile(ManagedFile):
  ...   def getTypeInfo(self):
  ...     class ti:
  ...       def getId(ti):
  ...         return self.type
  ...     return ti()

  >>> pt = getComponent(FS_PORTAL_TYPE, context=self.tool, wrapped=True)
  >>> path = pt.getPathFor

  >>> os.path.exists(join(dp, 'File', 'xxx_bar_xxx'))
  False

  >>> os.path.exists(join(dp, 'Document', 'xxx_bar_xxx'))
  False

  >>> file = PortalManagedFile
  >>> f = file(id='test', basedir='xxx_bar_xxx', type='File')
  >>> path(f, createpath=True) == join(dp, 'File', 'xxx_bar_xxx', 'test')
  True

  >>> os.path.exists(join(dp, 'File', 'xxx_bar_xxx'))
  True

  >>> os.path.isdir(join(dp, 'File', 'xxx_bar_xxx'))
  True

  >>> f = file(id='test', basedir='xxx_bar_xxx', type='Document')
  >>> path(f, createpath=True) == join(dp, 'Document', 'xxx_bar_xxx', 'test')
  True

  >>> os.path.exists(join(dp, 'Document', 'xxx_bar_xxx'))
  True

  >>> os.path.isdir(join(dp, 'Document', 'xxx_bar_xxx'))
  True

  >>> self.tool.cleanupFiles(join(dp, 'Document', 'xxx_bar_xxx', 'test'))
  >>> self.tool.cleanupFiles(join(dp, 'File', 'xxx_bar_xxx', 'test'))

WARNING: The next test may fail if you are running the tests on the
production instance, but it's very unlikely:

  >>> os.path.exists(join(dp, 'File', 'xxx_bar_xxx'))
  False

  >>> os.path.exists(join(dp, 'Document', 'xxx_bar_xxx'))
  False

