X-Git-Url: https://scm.cri.ensmp.fr/git/Plinn.git/blobdiff_plain/3c4367d8e03450e9a73e61f4247145d2b6c86a33..959d888c17d1403d2eeecc19bc4b5e2c8d1debf6:/Products/Plinn/HugePlinnFolder.py diff --git a/Products/Plinn/HugePlinnFolder.py b/Products/Plinn/HugePlinnFolder.py new file mode 100644 index 0000000..f2f5a92 --- /dev/null +++ b/Products/Plinn/HugePlinnFolder.py @@ -0,0 +1,239 @@ +# -*- coding: utf-8 -*- +####################################################################################### +# Plinn - http://plinn.org # +# Copyright (C) 2005-2007 Benoît PIN # +# # +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # +####################################################################################### +""" Plinn implementation of CMFBTree + + + +""" + + +from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base +from Products.ZCatalog.Lazy import LazyMap +from BTrees.IOBTree import IOBTree +from BTrees.OIBTree import OIBTree +from Folder import PlinnFolder +from zope.event import notify +try : + from zope.app.container.contained import notifyContainerModified +except ImportError : + ## Zope-2.13 compat + from zope.container.contained import notifyContainerModified +from events import ObjectPositionModified +from zope.component.factory import Factory +from Products.CMFCore.permissions import AddPortalFolders, \ + ManageProperties, \ + AccessContentsInformation +from AccessControl import ClassSecurityInfo +from Globals import InitializeClass +from types import StringType + + +class HugePlinnFolder(BTreeFolder2Base, PlinnFolder) : + """ Plinn Folder for large set of objects + """ + + security = ClassSecurityInfo() + + __getitem__ = PlinnFolder.__getitem__ + + def __init__(self, id, title='') : + PlinnFolder.__init__(self, id, title) + BTreeFolder2Base.__init__(self, id) + + def _initBTrees(self): + super(HugePlinnFolder, self)._initBTrees() + self._pos2id_index = IOBTree() + self._id2pos_index = OIBTree() + + def _checkId(self, id, allow_dup=0) : + PlinnFolder._checkId(self, id, allow_dup) + BTreeFolder2Base._checkId(self, id, allow_dup) + + security.declareProtected(AddPortalFolders, 'manage_addHugePlinnFolder') + def manage_addHugePlinnFolder(self, id, title='', REQUEST=None) : + """ Add new a new HugePlinnFolder object with id *id*. + """ + ob = HugePlinnFolder(id, title) + self._setObject(id, ob) + if REQUEST is not None : + return self.folder_contents(self, REQUEST, portal_status_message='Folder added') + + def _setOb(self, id, object): + super(HugePlinnFolder, self)._setOb(id, object) + pos = self.objectCount() - 1 + self._pos2id_index[pos] = id + self._id2pos_index[id] = pos + + def _delOb(self, id): + pos = self._id2pos_index[id] + self._id2pos_index.pop(id) + + for p in xrange(pos+1, self.objectCount()) : + ident = self._pos2id_index[p] + self._pos2id_index[p-1] = ident + self._id2pos_index[ident] = p-1 + + self._pos2id_index.pop(self.objectCount()-1) + + super(HugePlinnFolder, self)._delOb(id) + + security.declareProtected(AccessContentsInformation, 'objectIds') + def objectIds(self, spec=None) : + if spec is not None : + return super(HugePlinnFolder, self).objectIds(spec) + + pos2id = lambda pos : self._pos2id_index[pos] + return LazyMap(pos2id, xrange(self.objectCount())) + + + + security.declareProtected(ManageProperties, 'moveObjectsByDelta') + def moveObjectsByDelta(self, ids, delta, subset_ids=None, + suppress_events=False): + """ Move specified sub-objects by delta. + """ + if isinstance(ids, StringType): + ids = (ids,) + + id2pos = self._id2pos_index + pos2id = self._pos2id_index + for id in ids : + oldPosition = id2pos[id] + newPosition = max(oldPosition + delta, 0) + + shift = delta > 0 and 1 or -1 + for p in xrange(oldPosition, newPosition, shift) : + ident = pos2id[p+shift] + pos2id[p] = ident + id2pos[ident] = p + if not suppress_events : + notify(ObjectPositionModified(self[ident], self, p)) + + id2pos[id] = newPosition + pos2id[newPosition] = id + if not suppress_events : + notify(ObjectPositionModified(self[id], self, newPosition)) + + if not suppress_events : + notifyContainerModified(self) + + security.declareProtected(ManageProperties, 'moveObjectsAfter') + def moveObjectsAfter(self, ids, targetId, suppress_events=False): + assert targetId not in ids + + id2pos = self._id2pos_index + pos2id = self._pos2id_index + targetPos = id2pos[targetId] + minMovedPos = min([id2pos[id] for id in ids]) + maxMovedPos = max([id2pos[id] for id in ids]) + + for id in ids : + assert id == pos2id.pop(id2pos.pop(id)) + + id2posUpdate = {} + pos2idUpdate = {} + + if targetPos < minMovedPos : + # selection moved before the first item position + for i, id in enumerate(ids) : + pos = i + targetPos + 1 + id2posUpdate[id] = pos + pos2idUpdate[pos] = id + + for id in IndexIterator(pos2id, targetPos+1, maxMovedPos): + pos = pos + 1 + id2posUpdate[id] = pos + pos2idUpdate[pos] = id + + elif targetPos > minMovedPos and targetPos < maxMovedPos : + # selection moved between the first and last item positions + pos = minMovedPos + # move items placed between the first item position and the target position + for id in IndexIterator(pos2id, minMovedPos+1, targetPos) : + id2posUpdate[id] = pos + pos2idUpdate[pos] = id + pos += 1 + # move selected items + for id in ids : + id2posUpdate[id] = pos + pos2idUpdate[pos] = id + pos += 1 + # move items positioned between the target position and the moved item max position + for id in IndexIterator(pos2id, targetPos+1, maxMovedPos) : + id2posUpdate[id] = pos + pos2idUpdate[pos] = id + pos += 1 + + else : + # selection moved after the last item position + pos = minMovedPos + for id in IndexIterator(pos2id, minMovedPos+1, targetPos) : + id2posUpdate[id] = pos + pos2idUpdate[pos] = id + pos += 1 + + pos = targetPos - len(ids) + 1 + for id in ids : + id2posUpdate[id] = pos + pos2idUpdate[pos] = id + pos +=1 + + id2pos.update(id2posUpdate) + pos2id.update(pos2idUpdate) + + if not suppress_events : + for id, pos in id2posUpdate.items() : + notify(ObjectPositionModified(self[id], self, pos)) + + notifyContainerModified(self) + + def getObjectPosition(self, id): + """ Get the position of an object by its id. + """ + try : + return self._id2pos_index[id] + except KeyError : + raise ValueError('The object with the id "%s" does not exist.' % id) + + +class IndexIterator : + def __init__(self, d, start, stop) : + self.d = d + self.pos = start + self.stop = stop + + def __iter__(self) : + return self + + def next(self) : + try : + if self.pos > self.stop : + raise StopIteration + v = self.d[self.pos] + self.pos = self.pos + 1 + return v + except KeyError : + self.pos = self.pos + 1 + return self.next() + + +InitializeClass(HugePlinnFolder) +HugePlinnFolderFactory = Factory(HugePlinnFolder) +manage_addHugePlinnFolder = HugePlinnFolder.manage_addHugePlinnFolder.im_func