# # Copyright (c) 2007 Helmut Merz helmutm@cy55.de # # 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 # """ Utilities for managing version informations. $Id$ """ from BTrees.OOBTree import OOBTree from zope.component import adapts from zope.interface import implements, Attribute from zope.cachedescriptors.property import Lazy from zope.schema.interfaces import IField from zope.traversing.api import getName, getParent from cybertools.text.mimetypes import extensions from cybertools.typology.interfaces import IType from loops.interfaces import IResource from loops.versioning.interfaces import IVersionable _not_found = object() attrPattern = '__version_%s__' class VersionableResource(object): """ An adapter that enables a resource to store version information. """ implements(IVersionable) adapts(IResource) def __init__(self, context): self.context = context self.__parent__ = context def getVersioningAttribute(self, attr, default): attrName = attrPattern % attr value = getattr(self.context, attrName, _not_found) if value is _not_found: return default return value def setVersioningAttribute(self, attr, value): attrName = attrPattern % attr setattr(self.context, attrName, value) def initVersions(self): attrName = attrPattern % 'versions' value = getattr(self.context, attrName, _not_found) if value is _not_found: versions = OOBTree() versions['1.1'] = self.context setattr(self.context, attrName, versions) #self.versions['1.1'] = self.context @Lazy def versionNumbers(self): return self.getVersioningAttribute('versionNumbers', (1, 1)) @Lazy def variantIds(self): return self.getVersioningAttribute('variantIds', ()) @Lazy def versionId(self): versionPart = '.'.join(str(n) for n in self.versionNumbers) return '_'.join([versionPart] + list(self.variantIds)) @Lazy def master(self): return self.getVersioningAttribute('master', self.context) @Lazy def versionableMaster(self): """ The adapted master... """ return IVersionable(self.master) @Lazy def parent(self): return self.getVersioningAttribute('parent', None) @Lazy def comment(self): return self.getVersioningAttribute('comment', u'') @property def versions(self): return self.versionableMaster.getVersioningAttribute('versions', {}) @property def currentVersion(self): return self.versionableMaster.getVersioningAttribute('currentVersion', self.master) @property def releasedVersion(self): m = self.versionableMaster return self.versionableMaster.getVersioningAttribute('releasedVersion', None) def createVersionObject(self, versionNumbers, variantIds, comment=u''): versionableMaster = self.versionableMaster versionableMaster.initVersions() context = self.context # create object cls = context.__class__ obj = cls() # set versioning attributes of new object versionableObj = IVersionable(obj) versionableObj.setVersioningAttribute('versionNumbers', tuple(versionNumbers)) versionableObj.setVersioningAttribute('variantIds', variantIds) versionableObj.setVersioningAttribute('master', self.master) versionableObj.setVersioningAttribute('parent', context) versionableObj.setVersioningAttribute('comment', comment) # generate name for new object, register in parent versionId = versionableObj.versionId name = self.generateName(getName(self.master), extensions.get(context.contentType, ''), versionId) getParent(context)[name] = obj # record version on master self.versions[versionableObj.versionId] = obj # set resource attributes ti = IType(context).typeInterface attrs = set((ti and list(ti) or []) + ['title', 'description', 'data', 'contentType']) adaptedContext = ti and ti(context) or context adaptedObj = ti and ti(obj) or obj for attr in attrs: setattr(adaptedObj, attr, getattr(adaptedContext, attr)) return obj def createVersion(self, level=1, comment=u''): # get the new version numbers vn = list(IVersionable(self.currentVersion).versionNumbers) while len(vn) <= level: vn.append(1) vn[level] += 1 for l in range(level+1, len(vn)): # reset lower levels vn[l] = 1 # create new object obj = self.createVersionObject(vn, self.variantIds, comment) # set attributes of the master version self.versionableMaster.setVersioningAttribute('currentVersion', obj) return obj def generateName(self, name, ext, versionId): if ext: ext = '.' + ext if ext and name.endswith(ext): name = name[:-len(ext)] elif len(name) > 3 and name[-4] == '.': ext = name[-4:] name = name[:-4] return name + '_' + versionId + ext def cleanupVersions(context, event): """ Called upon deletion of a resource. """ #print 'cleaning up' vContext = IVersionable(context, None) if vContext is None: return rm = context.getLoopsRoot().getResourceManager() if context == vContext.master: toBeDeleted = [] for v in vContext.versions.values(): if v != context: toBeDeleted.append(getName(v)) for name in toBeDeleted: del rm[name] else: vId = vContext.versionId vMaster = IVersionable(vContext.master) del vMaster.versions[vId]