diff --git a/last_commit.txt b/last_commit.txt index aa1876bb6c..0d776d8d89 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,16 +1,48 @@ -Repository: mockup +Repository: zodbverify Branch: refs/heads/master -Date: 2022-07-06T22:59:01+02:00 -Author: Johannes Raggam (thet) -Commit: https://github.com/plone/mockup/commit/5f4661ab134a3e0ec2b391b0e19a4ff856606490 +Date: 2020-08-24T11:50:38+02:00 +Author: Philip Bauer (pbauer) +Commit: https://github.com/plone/zodbverify/commit/80e292a9fc2580193c769455b76722432461e169 -Release new version. +show all objects that reference a oid. follow the trail of references to referencing items up to the root. +This should give a idea where in the object-tree a item is actually located, how to access and fix it. Files changed: -M CHANGES.md -M package.json +M src/zodbverify/verify_oid.py -b'diff --git a/CHANGES.md b/CHANGES.md\nindex e62d323f4..86b0ca640 100644\n--- a/CHANGES.md\n+++ b/CHANGES.md\n@@ -1,5 +1,47 @@\n+# Changelog\n \n \n+\n+## [5.0.0-alpha.12](https://github.com/plone/mockup/compare/5.0.0-alpha.11...5.0.0-alpha.12) (2022-07-06)\n+\n+\n+### Maintenance\n+\n+\n+* Adapt to @patternslib/dev module federation changes. ([730ab0f](https://github.com/plone/mockup/commit/730ab0f4c4b214bd39a1e266ab0123578c4fb606))\n+\n+* **Build:** Upgrade dependencies. ([b297ac4](https://github.com/plone/mockup/commit/b297ac46687130a45b72e89019b9fa43a78f2d3a))\n+\n+* **Build:** Upgrade Patterns to 9.0.0-beta.0 and pat-code-editor to 3.0.0. ([d6996bf](https://github.com/plone/mockup/commit/d6996bf5bf1ea244f9e216ff071e62b7964655c7))\n+\n+* **Cleanup:** prettier whole code base. ([e19f23e](https://github.com/plone/mockup/commit/e19f23e5ef79197810422f66e70127c77a0c7ec8))\n+\n+* **Cleanup:** Remove dependency regenerator-runtime except from test setup. The async/await runtime handling is already built-in in current Babel. ([e848c13](https://github.com/plone/mockup/commit/e848c13a063bad6eea980bfaae90413f53159cfa))\n+\n+* **Cleanup:** Remove unused dependencies. ([a5ef3f2](https://github.com/plone/mockup/commit/a5ef3f21f86cd58e7e19093f3a5d6abcbbe925d4))\n+\n+* **Cleanup:** Remove unused imports and variables. ([9b79ce3](https://github.com/plone/mockup/commit/9b79ce3808ff68acd2e8411cd455981a6aeaa78c))\n+\n+* **Cleanup:** Remove unused r.js file. ([e6322d3](https://github.com/plone/mockup/commit/e6322d3dd7d8f43220fdd5997d474a495b45e4cc))\n+\n+* Depend on @patternslib/dev and extend from there. ([2f2ef5f](https://github.com/plone/mockup/commit/2f2ef5fe47406713340d044444d35fceeb3313ee))\n+\n+* Extend babel config from @patternslib/dev. ([b702492](https://github.com/plone/mockup/commit/b702492f1b614b60c1e058b093ec1c818375ed11))\n+\n+* Extend commitlint config from @patternslib/dev. ([4330cf7](https://github.com/plone/mockup/commit/4330cf7b99d91e22ae38d72bc7e982856162584c))\n+\n+* Extend eslint config from @patternslib/dev. ([857f678](https://github.com/plone/mockup/commit/857f678f314e5d41eac182a676447a18402b1f77))\n+\n+* Extend jest config from @patternslib/dev. ([5c43d43](https://github.com/plone/mockup/commit/5c43d43d5c39f6fc123f3dd900b00082ceb8cc24))\n+\n+* Extend Makefile from @patternslib/dev. ([5da74f4](https://github.com/plone/mockup/commit/5da74f4e76fec1c66b6707be6479a61bfcecfc11))\n+\n+* Extend prettier config from @patternslib/dev. ([08ce446](https://github.com/plone/mockup/commit/08ce4466d8594200df16ad4538fc67cc6adc3471))\n+\n+* Extend release-it config from @patternslib/dev. ([ec69436](https://github.com/plone/mockup/commit/ec69436c837516cc654f11e4c8104b13bd7cbaf1))\n+\n+* Extend webpack config from @patternslib/dev. ([811673a](https://github.com/plone/mockup/commit/811673a0db4e0133ea19adee2bd6b84cc04ddbda))\n+\n ## [5.0.0-alpha.11](https://github.com/plone/mockup/compare/5.0.0-alpha.10...5.0.0-alpha.11) (2022-06-29)\n \n \ndiff --git a/package.json b/package.json\nindex b5e88a80a..4a49b61e4 100644\n--- a/package.json\n+++ b/package.json\n@@ -1,6 +1,6 @@\n {\n "name": "@plone/mockup",\n- "version": "5.0.0-alpha.11",\n+ "version": "5.0.0-alpha.12",\n "description": "A collection of client side patterns for faster and easier web development",\n "license": "BSD-3-Clause",\n "main": "./src/patterns.js",\n' +b'diff --git a/src/zodbverify/verify_oid.py b/src/zodbverify/verify_oid.py\nindex 2d865ff..34b3000 100644\n--- a/src/zodbverify/verify_oid.py\n+++ b/src/zodbverify/verify_oid.py\n@@ -1,5 +1,8 @@\n # -*- coding: utf-8 -*-\n+from collections import defaultdict\n from ZODB.interfaces import IStorageCurrentRecordIteration\n+from ZODB.serialize import get_refs\n+from ZODB.utils import get_pickle_metadata\n from ZODB.utils import oid_repr\n from ZODB.utils import p64\n from zodbverify.verify import verify_record\n@@ -13,6 +16,58 @@\n logger = logging.getLogger("zodbverify")\n \n \n+class Refbuilder(object):\n+\n+ def __init__(self, storage):\n+ self.storage = storage\n+ self.seen = []\n+ self.refs = defaultdict(list)\n+ self.stop_recurse = [\n+ p64(0x00), # persistent.mapping.PersistentMapping\n+ p64(0x01), # OFS.Application.Application\n+ p64(0x11), # Products.CMFPlone.Portal.PloneSite\n+ ]\n+\n+ def build_ref_tree(self):\n+ logger.info(\'Building a reference-tree of ZODB...\')\n+ count = 0\n+ next_ = None\n+ while True:\n+ count += 1\n+ oid, tid, data, next_ = self.storage.record_iternext(next_)\n+\n+ # For each oid create a list of oids that reference it\n+ # Can be used for reverse lookup similar to what fsoids.py\n+ # in ZODB does.\n+ # We store integers since that uses less memory.\n+ oid_refs = get_refs(data)\n+ if oid_refs:\n+ for referenced_oid, class_info in oid_refs:\n+ self.refs[referenced_oid].append(oid)\n+ if next_ is None:\n+ break\n+ if not count % 5000:\n+ logger.info(\'Objects: {}\'.format(count))\n+ logger.info(\'Created a reference-dict for {} objects.\\n\'.format(count))\n+\n+ def get_refs(self, oid, level=0, max_level=10):\n+ for ref in self.refs[oid]:\n+ if ref in self.seen:\n+ continue\n+ level += 1\n+ if level > max_level:\n+ logger.info(\' 8< --------------- >8 Stop after level {}!\\n\'.format(max_level))\n+ continue\n+ self.seen.append(ref)\n+ pick, state = self.storage.load(ref)\n+ class_info = \'%s.%s\' % get_pickle_metadata(pick)\n+ logger.info(\'{} {} at level {}\'.format(oid_repr(ref), class_info, level))\n+ if oid in self.stop_recurse:\n+ logger.info(\' 8< --------------- >8 Stop at root objects\\n\')\n+ continue\n+ self.get_refs(ref, level)\n+\n+\n def verify_oid(storage, oid, debug=False, app=None):\n if not IStorageCurrentRecordIteration.providedBy(storage):\n raise TypeError(\n@@ -60,3 +115,8 @@ def verify_oid(storage, oid, debug=False, app=None):\n success, msg = verify_record(oid, pickle, debug)\n if not success:\n logger.info(\'{}: {}\'.format(msg, oid_repr(oid)))\n+\n+ refbuilder = Refbuilder(storage)\n+ refbuilder.build_ref_tree()\n+ logger.info(\'\\nThis oid is referenced by:\\n\')\n+ refbuilder.get_refs(oid)\n' + +Repository: zodbverify + + +Branch: refs/heads/master +Date: 2020-08-24T11:50:38+02:00 +Author: Philip Bauer (pbauer) +Commit: https://github.com/plone/zodbverify/commit/c38a20935eb4fab5c0ca238214338f30e5321806 + +Add file-cache of reftree and add more information on refs (name). Mostly stolen from collective.zodbdebug + +Files changed: +M src/zodbverify/verify_oid.py + +b'diff --git a/src/zodbverify/verify_oid.py b/src/zodbverify/verify_oid.py\nindex 34b3000..d154ba9 100644\n--- a/src/zodbverify/verify_oid.py\n+++ b/src/zodbverify/verify_oid.py\n@@ -4,10 +4,14 @@\n from ZODB.serialize import get_refs\n from ZODB.utils import get_pickle_metadata\n from ZODB.utils import oid_repr\n+from ZODB.utils import repr_to_oid\n from ZODB.utils import p64\n+from ZODB.utils import tid_repr\n from zodbverify.verify import verify_record\n \n+import json\n import logging\n+import os\n import pdb\n import traceback\n import ZODB\n@@ -18,17 +22,24 @@\n \n class Refbuilder(object):\n \n- def __init__(self, storage):\n+ def __init__(self, storage, connection):\n self.storage = storage\n+ self.connection = connection\n self.seen = []\n self.refs = defaultdict(list)\n+ self.msg = []\n self.stop_recurse = [\n- p64(0x00), # persistent.mapping.PersistentMapping\n- p64(0x01), # OFS.Application.Application\n- p64(0x11), # Products.CMFPlone.Portal.PloneSite\n+ \'0x00\', # persistent.mapping.PersistentMapping\n+ \'0x01\', # OFS.Application.Application\n+ \'0x11\', # Products.CMFPlone.Portal.PloneSite\n ]\n \n- def build_ref_tree(self):\n+ def get_oid_report(self, oid, level=0, max_level=600, verbose=False):\n+ self.msg.append(\'The oid {} is referenced by:\\n\'.format(oid))\n+ self.inspect_reference_tree(oid, level=0, max_level=600, verbose=verbose)\n+ logger.info(\'\\n\'.join(self.msg))\n+\n+ def _build_ref_tree(self):\n logger.info(\'Building a reference-tree of ZODB...\')\n count = 0\n next_ = None\n@@ -39,33 +50,141 @@ def build_ref_tree(self):\n # For each oid create a list of oids that reference it\n # Can be used for reverse lookup similar to what fsoids.py\n # in ZODB does.\n- # We store integers since that uses less memory.\n oid_refs = get_refs(data)\n if oid_refs:\n for referenced_oid, class_info in oid_refs:\n- self.refs[referenced_oid].append(oid)\n+ self.refs[oid_repr(referenced_oid)].append(oid_repr(oid))\n if next_ is None:\n break\n- if not count % 5000:\n+ if not count % 10000:\n logger.info(\'Objects: {}\'.format(count))\n logger.info(\'Created a reference-dict for {} objects.\\n\'.format(count))\n \n- def get_refs(self, oid, level=0, max_level=10):\n+ def inspect_reference_tree(self, oid, level=0, max_level=600, verbose=False):\n+ if oid not in self.refs:\n+ logger.debug(\'The oid {} does not exist!\'.format(oid))\n+ return\n+ child_pickle, state = self.storage.load(repr_to_oid(oid))\n+ child_class_info = \'%s.%s\' % get_pickle_metadata(child_pickle)\n+\n for ref in self.refs[oid]:\n if ref in self.seen:\n continue\n level += 1\n if level > max_level:\n- logger.info(\' 8< --------------- >8 Stop after level {}!\\n\'.format(max_level))\n+ msg = \'8< --------------- >8 Stop after level {}!\\n\'.format(max_level)\n+ self.msg.append(msg)\n+ logger.debug(msg)\n continue\n self.seen.append(ref)\n- pick, state = self.storage.load(ref)\n+ pick, state = self.storage.load(repr_to_oid(ref))\n class_info = \'%s.%s\' % get_pickle_metadata(pick)\n- logger.info(\'{} {} at level {}\'.format(oid_repr(ref), class_info, level))\n+ name = None\n+ if verbose:\n+ name = self.get_id_or_attr_name(oid=oid, parent_oid=ref)\n+\n+ if name:\n+ msg = \'{} ({}) is {} for {} ({}) at level {}\'.format(oid, child_class_info, name, ref, class_info, level)\n+ else:\n+ msg = \'{} ({}) is referenced by {} ({}) at level {}\'.format(oid, child_class_info, ref, class_info, level)\n+ self.msg.append(msg)\n+ logger.debug(msg)\n+\n if oid in self.stop_recurse:\n- logger.info(\' 8< --------------- >8 Stop at root objects\\n\')\n+ msg = \'8< --------------- >8 Stop at root objects\'\n+ self.msg.append(msg)\n+ logger.debug(msg)\n continue\n- self.get_refs(ref, level)\n+ self.inspect_reference_tree(ref, level=level, verbose=verbose)\n+\n+ @property\n+ def root_oid(self):\n+ return self.connection.root._root._p_oid\n+\n+ def oid_or_repr_to_oid(self, oid_or_repr):\n+ if isinstance(oid_or_repr, bytes):\n+ return oid_or_repr\n+ return repr_to_oid(oid_or_repr)\n+\n+ def oid_or_repr_to_repr(self, oid_or_repr):\n+ if isinstance(oid_or_repr, bytes):\n+ return oid_repr(oid_or_repr)\n+ return oid_or_repr\n+\n+ # Do not use cache decorators, let ZODB do its caching.\n+ def get_obj(self, oid):\n+ u"""Get the object from its `oid\'."""\n+ oid = self.oid_or_repr_to_oid(oid)\n+ obj = self.connection.get(oid)\n+ obj._p_activate()\n+ return obj\n+\n+ # @instance.memoize\n+ def get_obj_as_str(self, oid):\n+ try:\n+ return str(self.get_obj(oid))\n+ except Exception:\n+ return \'\'\n+\n+ # @instance.memoize\n+ def get_physical_path(self, oid):\n+ try:\n+ return self.get_obj(oid).getPhysicalPath()\n+ except Exception:\n+ return None\n+\n+ # @instance.memoize\n+ def get_id(self, oid):\n+ obj = self.get_obj(oid)\n+ if oid == self.root_oid:\n+ return \'Root\'\n+ getId = getattr(obj, \'getId\', None)\n+ if getId:\n+ try:\n+ return getId()\n+ except: # noqa\n+ pass\n+ return getattr(obj, \'id\', None)\n+\n+ # @instance.memoize\n+ def get_attr_name(self, oid, parent_oid):\n+ oid = self.oid_or_repr_to_oid(oid)\n+ obj = self.get_obj(oid)\n+ parent = self.get_obj(parent_oid)\n+ names_and_values = ((name, getattr(parent, name, None)) for name in dir(parent))\n+ return next((name for (name, value) in names_and_values if value is obj), None)\n+\n+ # @instance.memoize\n+ def get_id_or_attr_name(self, oid, parent_oid=None):\n+ identifier = self.get_id(oid)\n+ if identifier:\n+ return identifier\n+\n+ return self.get_attr_name(oid, parent_oid) if parent_oid else None\n+\n+ def load_reference_tree(self):\n+ path = self._get_reference_cache_path()\n+ if os.path.exists(path):\n+ with open(path, \'r\') as f:\n+ logger.info(\'Loading json reference-cache from {}\'.format(path))\n+ self.refs = json.load(f)\n+ else:\n+ self._build_ref_tree()\n+ self._store_reference_cache()\n+\n+ def _store_reference_cache(self):\n+ path = self._get_reference_cache_path()\n+ dirname = os.path.dirname(path)\n+ if not os.path.exists(dirname):\n+ os.makedirs(dirname)\n+ with open(path, \'w\') as f:\n+ json.dump(self.refs, f)\n+ logger.info(\'Save reference-cache as {}\'.format(path))\n+\n+ def _get_reference_cache_path(self):\n+ cache_dir = os.path.join(os.path.expanduser(\'~\'), \'.cache\', \'zodbverify\')\n+ last_tid = tid_repr(self.storage.lastTransaction())\n+ return os.path.join(cache_dir, \'zodb_references_{}.json\'.format(last_tid))\n \n \n def verify_oid(storage, oid, debug=False, app=None):\n@@ -77,14 +196,12 @@ def verify_oid(storage, oid, debug=False, app=None):\n try:\n # by default expect a 8-byte string (e.g. \'0x22d17d\')\n # transform to a 64-bit long integer (e.g. b\'\\x00\\x00\\x00\\x00\\x00"\\xd1}\')\n- as_int = int(oid, 0)\n- oid = p64(as_int)\n- except ValueError:\n- # probably already a 64-bit long integer\n+ oid = repr_to_oid(oid)\n+ except:\n pass\n \n if app:\n- # use exitsing zope instance.\n+ # use existing zope instance.\n # only available when used as ./bin/instance zodbverify -o XXX\n connection = app._p_jar\n else:\n@@ -116,7 +233,6 @@ def verify_oid(storage, oid, debug=False, app=None):\n if not success:\n logger.info(\'{}: {}\'.format(msg, oid_repr(oid)))\n \n- refbuilder = Refbuilder(storage)\n- refbuilder.build_ref_tree()\n- logger.info(\'\\nThis oid is referenced by:\\n\')\n- refbuilder.get_refs(oid)\n+ refbuilder = Refbuilder(storage, connection)\n+ refbuilder.load_reference_tree()\n+ refbuilder.get_oid_report(oid_repr(oid), verbose=True)\n' + +Repository: zodbverify + + +Branch: refs/heads/master +Date: 2022-07-06T23:55:35+02:00 +Author: Maurits van Rees (mauritsvanrees) +Commit: https://github.com/plone/zodbverify/commit/8e8a3b1a6863fa10f8349b4dfef58aa87a82e188 + +Merge pull request #8 from plone/show_references + +show all objects that reference a oid + +Files changed: +M src/zodbverify/verify_oid.py + +b'diff --git a/src/zodbverify/verify_oid.py b/src/zodbverify/verify_oid.py\nindex 2d865ff..d154ba9 100644\n--- a/src/zodbverify/verify_oid.py\n+++ b/src/zodbverify/verify_oid.py\n@@ -1,10 +1,17 @@\n # -*- coding: utf-8 -*-\n+from collections import defaultdict\n from ZODB.interfaces import IStorageCurrentRecordIteration\n+from ZODB.serialize import get_refs\n+from ZODB.utils import get_pickle_metadata\n from ZODB.utils import oid_repr\n+from ZODB.utils import repr_to_oid\n from ZODB.utils import p64\n+from ZODB.utils import tid_repr\n from zodbverify.verify import verify_record\n \n+import json\n import logging\n+import os\n import pdb\n import traceback\n import ZODB\n@@ -13,6 +20,173 @@\n logger = logging.getLogger("zodbverify")\n \n \n+class Refbuilder(object):\n+\n+ def __init__(self, storage, connection):\n+ self.storage = storage\n+ self.connection = connection\n+ self.seen = []\n+ self.refs = defaultdict(list)\n+ self.msg = []\n+ self.stop_recurse = [\n+ \'0x00\', # persistent.mapping.PersistentMapping\n+ \'0x01\', # OFS.Application.Application\n+ \'0x11\', # Products.CMFPlone.Portal.PloneSite\n+ ]\n+\n+ def get_oid_report(self, oid, level=0, max_level=600, verbose=False):\n+ self.msg.append(\'The oid {} is referenced by:\\n\'.format(oid))\n+ self.inspect_reference_tree(oid, level=0, max_level=600, verbose=verbose)\n+ logger.info(\'\\n\'.join(self.msg))\n+\n+ def _build_ref_tree(self):\n+ logger.info(\'Building a reference-tree of ZODB...\')\n+ count = 0\n+ next_ = None\n+ while True:\n+ count += 1\n+ oid, tid, data, next_ = self.storage.record_iternext(next_)\n+\n+ # For each oid create a list of oids that reference it\n+ # Can be used for reverse lookup similar to what fsoids.py\n+ # in ZODB does.\n+ oid_refs = get_refs(data)\n+ if oid_refs:\n+ for referenced_oid, class_info in oid_refs:\n+ self.refs[oid_repr(referenced_oid)].append(oid_repr(oid))\n+ if next_ is None:\n+ break\n+ if not count % 10000:\n+ logger.info(\'Objects: {}\'.format(count))\n+ logger.info(\'Created a reference-dict for {} objects.\\n\'.format(count))\n+\n+ def inspect_reference_tree(self, oid, level=0, max_level=600, verbose=False):\n+ if oid not in self.refs:\n+ logger.debug(\'The oid {} does not exist!\'.format(oid))\n+ return\n+ child_pickle, state = self.storage.load(repr_to_oid(oid))\n+ child_class_info = \'%s.%s\' % get_pickle_metadata(child_pickle)\n+\n+ for ref in self.refs[oid]:\n+ if ref in self.seen:\n+ continue\n+ level += 1\n+ if level > max_level:\n+ msg = \'8< --------------- >8 Stop after level {}!\\n\'.format(max_level)\n+ self.msg.append(msg)\n+ logger.debug(msg)\n+ continue\n+ self.seen.append(ref)\n+ pick, state = self.storage.load(repr_to_oid(ref))\n+ class_info = \'%s.%s\' % get_pickle_metadata(pick)\n+ name = None\n+ if verbose:\n+ name = self.get_id_or_attr_name(oid=oid, parent_oid=ref)\n+\n+ if name:\n+ msg = \'{} ({}) is {} for {} ({}) at level {}\'.format(oid, child_class_info, name, ref, class_info, level)\n+ else:\n+ msg = \'{} ({}) is referenced by {} ({}) at level {}\'.format(oid, child_class_info, ref, class_info, level)\n+ self.msg.append(msg)\n+ logger.debug(msg)\n+\n+ if oid in self.stop_recurse:\n+ msg = \'8< --------------- >8 Stop at root objects\'\n+ self.msg.append(msg)\n+ logger.debug(msg)\n+ continue\n+ self.inspect_reference_tree(ref, level=level, verbose=verbose)\n+\n+ @property\n+ def root_oid(self):\n+ return self.connection.root._root._p_oid\n+\n+ def oid_or_repr_to_oid(self, oid_or_repr):\n+ if isinstance(oid_or_repr, bytes):\n+ return oid_or_repr\n+ return repr_to_oid(oid_or_repr)\n+\n+ def oid_or_repr_to_repr(self, oid_or_repr):\n+ if isinstance(oid_or_repr, bytes):\n+ return oid_repr(oid_or_repr)\n+ return oid_or_repr\n+\n+ # Do not use cache decorators, let ZODB do its caching.\n+ def get_obj(self, oid):\n+ u"""Get the object from its `oid\'."""\n+ oid = self.oid_or_repr_to_oid(oid)\n+ obj = self.connection.get(oid)\n+ obj._p_activate()\n+ return obj\n+\n+ # @instance.memoize\n+ def get_obj_as_str(self, oid):\n+ try:\n+ return str(self.get_obj(oid))\n+ except Exception:\n+ return \'\'\n+\n+ # @instance.memoize\n+ def get_physical_path(self, oid):\n+ try:\n+ return self.get_obj(oid).getPhysicalPath()\n+ except Exception:\n+ return None\n+\n+ # @instance.memoize\n+ def get_id(self, oid):\n+ obj = self.get_obj(oid)\n+ if oid == self.root_oid:\n+ return \'Root\'\n+ getId = getattr(obj, \'getId\', None)\n+ if getId:\n+ try:\n+ return getId()\n+ except: # noqa\n+ pass\n+ return getattr(obj, \'id\', None)\n+\n+ # @instance.memoize\n+ def get_attr_name(self, oid, parent_oid):\n+ oid = self.oid_or_repr_to_oid(oid)\n+ obj = self.get_obj(oid)\n+ parent = self.get_obj(parent_oid)\n+ names_and_values = ((name, getattr(parent, name, None)) for name in dir(parent))\n+ return next((name for (name, value) in names_and_values if value is obj), None)\n+\n+ # @instance.memoize\n+ def get_id_or_attr_name(self, oid, parent_oid=None):\n+ identifier = self.get_id(oid)\n+ if identifier:\n+ return identifier\n+\n+ return self.get_attr_name(oid, parent_oid) if parent_oid else None\n+\n+ def load_reference_tree(self):\n+ path = self._get_reference_cache_path()\n+ if os.path.exists(path):\n+ with open(path, \'r\') as f:\n+ logger.info(\'Loading json reference-cache from {}\'.format(path))\n+ self.refs = json.load(f)\n+ else:\n+ self._build_ref_tree()\n+ self._store_reference_cache()\n+\n+ def _store_reference_cache(self):\n+ path = self._get_reference_cache_path()\n+ dirname = os.path.dirname(path)\n+ if not os.path.exists(dirname):\n+ os.makedirs(dirname)\n+ with open(path, \'w\') as f:\n+ json.dump(self.refs, f)\n+ logger.info(\'Save reference-cache as {}\'.format(path))\n+\n+ def _get_reference_cache_path(self):\n+ cache_dir = os.path.join(os.path.expanduser(\'~\'), \'.cache\', \'zodbverify\')\n+ last_tid = tid_repr(self.storage.lastTransaction())\n+ return os.path.join(cache_dir, \'zodb_references_{}.json\'.format(last_tid))\n+\n+\n def verify_oid(storage, oid, debug=False, app=None):\n if not IStorageCurrentRecordIteration.providedBy(storage):\n raise TypeError(\n@@ -22,14 +196,12 @@ def verify_oid(storage, oid, debug=False, app=None):\n try:\n # by default expect a 8-byte string (e.g. \'0x22d17d\')\n # transform to a 64-bit long integer (e.g. b\'\\x00\\x00\\x00\\x00\\x00"\\xd1}\')\n- as_int = int(oid, 0)\n- oid = p64(as_int)\n- except ValueError:\n- # probably already a 64-bit long integer\n+ oid = repr_to_oid(oid)\n+ except:\n pass\n \n if app:\n- # use exitsing zope instance.\n+ # use existing zope instance.\n # only available when used as ./bin/instance zodbverify -o XXX\n connection = app._p_jar\n else:\n@@ -60,3 +232,7 @@ def verify_oid(storage, oid, debug=False, app=None):\n success, msg = verify_record(oid, pickle, debug)\n if not success:\n logger.info(\'{}: {}\'.format(msg, oid_repr(oid)))\n+\n+ refbuilder = Refbuilder(storage, connection)\n+ refbuilder.load_reference_tree()\n+ refbuilder.get_oid_report(oid_repr(oid), verbose=True)\n'