Plone CMS 4.x onwards stores uploaded files and images in ZODB as BLOBs. They exist in var/blobstorage folder structure on the file system, files being named after persistent object ids (non-human-readable). The objects themselves, without file payload, are stored in an append-only database file called filestorage and usually the name of this file is Data.fs.
If you copy Plone site database object data (Data.fs) and forget to copy blobstorage folder, or data gets out of the sync during the copy, various problems appear on the Plone site
- You cannot access a content item which has a corresponding blob file missing on the file system
- You cannot rebuild the portal_catalog indexes
- Database packing may fail
Instead, you’ll see something like this – an evil POSKeyError exception (Persistent Object Storage):
Traceback (most recent call last): File "/fast/xxx/eggs/ZODB3-3.10.3-py2.6-macosx-10.6-i386.egg/ZODB/Connection.py", line 860, in setstate self._setstate(obj) File "/fast/xxx/eggs/ZODB3-3.10.3-py2.6-macosx-10.6-i386.egg/ZODB/Connection.py", line 922, in _setstate obj._p_blob_committed = self._storage.loadBlob(obj._p_oid, serial) File "/fast/xxx/eggs/ZODB3-3.10.3-py2.6-macosx-10.6-i386.egg/ZODB/blob.py", line 644, in loadBlob raise POSKeyError("No blob file", oid, serial) POSKeyError: 'No blob file'
The proper solution is to fix this problem is to
- Re-copy blobstorage folder
- Restart Plone twice in foreground mode (sometimes freshly copied blobstorage folder does not get picked up – some kind of timestamp issue?)
- Plone site copy instructions
However you may have failed. You may have damaged or lost your blobstorage forever. To get the Plone site to a working state all content having bad BLOB data must be deleted (usually meaning losing some of site images and uploaded files).
Below is Python code for Grok view which you can drop in to your own Plone add-on product. It creates an admin view which you can call directly thru URL. This code will walk thru all the content on your Plone site and tries to delete bad content items with BLOBs missing.
The code handles both Archetypes and Dexterity subsystems’ content types.
Note: Fixing Dexterity blobs with this code have never been tested – please feel free to update the code in collective.developermanual on GitHub if you find it not working properly.
The code, fixblobs.py:
""" A Zope command line script to delete content with missing BLOB in Plone, causing POSKeyErrors when content is being accessed or during portal_catalog rebuild. Tested on Plone 4.1 + Dexterity 1.1. http://stackoverflow.com/questions/8655675/cleaning-up-poskeyerror-no-blob-file-content-from-plone-site Also see: http://pypi.python.org/pypi/experimental.gracefulblobmissing/ """ # Zope imports from ZODB.POSException import POSKeyError from zope.component import getMultiAdapter from zope.component import queryUtility from Products.CMFCore.interfaces import IPropertiesTool from Products.CMFCore.interfaces import IFolderish, ISiteRoot # Plone imports from five import grok from Products.Archetypes.Field import FileField from Products.Archetypes.interfaces import IBaseContent from plone.namedfile.interfaces import INamedFile from plone.dexterity.content import DexterityContent def check_at_blobs(context): """ Archetypes content checker. Return True if purge needed """ if IBaseContent.providedBy(context): schema = context.Schema() for field in schema.fields(): id = field.getName() if isinstance(field, FileField): try: field.get_size(context) except POSKeyError: print "Found damaged AT FileField %s on %s" % (id, context.absolute_url()) return True return False def check_dexterity_blobs(context): """ Check Dexterity content for damaged blob fields XXX: NOT TESTED - THEORETICAL, GUIDELINING, IMPLEMENTATION Return True if purge needed """ # Assume dexterity contennt inherits from Item if isinstance(context, DexterityContent): # Iterate through all Python object attributes # XXX: Might be smarter to use zope.schema introspection here? for key, value in context.__dict__.items(): # Ignore non-contentish attributes to speed up us a bit if not key.startswith("_"): if INamedFile.providedBy(value): try: value.getSize() except POSKeyError: print "Found damaged Dexterity plone.app.NamedFile %s on %s" % (key, context.absolute_url()) return True return False def fix_blobs(context): """ Iterate through the object variables and see if they are blob fields and if the field loading fails then poof """ if check_at_blobs(context) or check_dexterity_blobs(context): print "Bad blobs found on %s" % context.absolute_url() + " -> deleting" parent = context.aq_parent parent.manage_delObjects([context.getId()]) def recurse(tree): """ Walk through all the content on a Plone site """ for id, child in tree.contentItems(): fix_blobs(child) if IFolderish.providedBy(child): recurse(child) class FixBlobs(grok.CodeView): """ A management view to clean up content with damaged BLOB files You can call this view by 1) Starting Plone in debug mode (console output available) 2) Visit site.com/@@fix-blobs URL """ grok.name("fix-blobs") grok.context(ISiteRoot) grok.require("cmf.ManagePortal") def disable_integrity_check(self): """ Content HTML may have references to this broken image - we cannot fix that HTML but link integriry check will yell if we try to delete the bad image. http://collective-docs.readthedocs.org/en/latest/content/deleting.html#bypassing-link-integrity-check " """ ptool = queryUtility(IPropertiesTool) props = getattr(ptool, 'site_properties', None) self.old_check = props.getProperty('enable_link_integrity_checks', False) props.enable_link_integrity_checks = False def enable_integrity_check(self): """ """ ptool = queryUtility(IPropertiesTool) props = getattr(ptool, 'site_properties', None) props.enable_link_integrity_checks = self.old_check def render(self): #plone = getMultiAdapter((self.context, self.request), name="plone_portal_state") print "Checking blobs" portal = self.context self.disable_integrity_check() recurse(portal) self.enable_integrity_check() print "All done" return "OK - check console for status messages"