Skip to content

Commit

Permalink
Merge branch '4.x' into OFS_avoid_direct_id_access#903
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Howitz committed Oct 9, 2020
2 parents a9f13a7 + 6e96a9a commit 111666e
Show file tree
Hide file tree
Showing 13 changed files with 514 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -13,6 +13,9 @@ https://zope.readthedocs.io/en/2.13/CHANGES.html
- Replace (in ``OFS``) the deprecated direct ``id`` access by
``getId`` calls.

- Restore the ZMI `Debug Information` control panel page
(`#898 <https://github.com/zopefoundation/Zope/issues/898>`_)

- HTTP header encoding support
(`#905 <https://github.com/zopefoundation/Zope/pull/905>`_)

Expand Down
2 changes: 1 addition & 1 deletion constraints.txt
Expand Up @@ -12,7 +12,7 @@ Paste==3.4.3
PasteDeploy==2.1.0
Persistence==3.0
Products.BTreeFolder2==4.2
Products.ZCatalog==5.1
Products.ZCatalog==5.2
Record==3.5
RestrictedPython==5.0
WSGIProxy2==0.4.6
Expand Down
2 changes: 1 addition & 1 deletion requirements-full.txt
Expand Up @@ -13,7 +13,7 @@ Paste==3.4.3
PasteDeploy==2.1.0
Persistence==3.0
Products.BTreeFolder2==4.2
Products.ZCatalog==5.1
Products.ZCatalog==5.2
Record==3.5
RestrictedPython==5.0
WSGIProxy2==0.4.6
Expand Down
4 changes: 2 additions & 2 deletions sources.cfg
Expand Up @@ -4,7 +4,7 @@ github_push = git@github.com:zopefoundation

[sources]
# Zope-specific
AccessControl = git ${remotes:github}/AccessControl pushurl=${remotes:github_push}/AccessControl
AccessControl = git ${remotes:github}/AccessControl pushurl=${remotes:github_push}/AccessControl branch=4.x
Acquisition = git ${remotes:github}/Acquisition pushurl=${remotes:github_push}/Acquisition
AuthEncoding = git ${remotes:github}/AuthEncoding pushurl=${remotes:github_push}/AuthEncoding
DateTime = git ${remotes:github}/DateTime pushurl=${remotes:github_push}/DateTime
Expand All @@ -23,7 +23,7 @@ Missing = git ${remotes:github}/Missing pushurl=${remotes:github_push}/Missing
Products.BTreeFolder2 = git ${remotes:github}/Products.BTreeFolder2 pushurl=${remotes:github_push}/Products.BTreeFolder2
Products.MailHost = git ${remotes:github}/Products.MailHost pushurl=${remotes:github_push}/Products.MailHost
Products.PythonScripts = git ${remotes:github}/Products.PythonScripts pushurl=${remotes:github_push}/Products.PythonScripts
Products.ZCatalog = git ${remotes:github}/Products.ZCatalog pushurl=${remotes:github_push}/Products.ZCatalog
Products.ZCatalog = git ${remotes:github}/Products.ZCatalog pushurl=${remotes:github_push}/Products.ZCatalog branch=5.x
Products.Sessions = git ${remotes:github}/Products.Sessions pushurl=${remotes:github_push}/Products.Sessions
Products.TemporaryFolder = git ${remotes:github}/Products.TemporaryFolder pushurl=${remotes:github_push}/Products.TemporaryFolder
Products.ZODBMountPoint = git ${remotes:github}/Products.ZODBMountPoint pushurl=${remotes:github_push}/Products.ZODBMountPoint
Expand Down
115 changes: 114 additions & 1 deletion src/App/ApplicationManager.py
Expand Up @@ -14,18 +14,23 @@
import os
import sys
import time
import types

from six import class_types
from six.moves._thread import get_ident
from six.moves.urllib import parse

from AccessControl.class_init import InitializeClass
from AccessControl.requestmethod import requestmethod
from Acquisition import Implicit
from App.CacheManager import CacheManager
from App.config import getConfiguration
from App.DavLockManager import DavLockManager
from App.Management import Tabs
from App.special_dtml import DTMLFile
from App.Undo import UndoSupport
from App.version_txt import version_txt
from DateTime.DateTime import DateTime
from OFS.Traversable import Traversable
from Persistence import Persistent
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
Expand Down Expand Up @@ -59,6 +64,8 @@ class DatabaseChooser(Tabs, Traversable, Implicit):
{'label': 'Control Panel', 'action': '../manage_main'},
{'label': 'Databases', 'action': 'manage_main'},
{'label': 'Configuration', 'action': '../Configuration/manage_main'},
{'label': 'DAV Locks', 'action': '../DAVLocks/manage_main'},
{'label': 'Debug Information', 'action': '../DebugInfo/manage_main'},
)
MANAGE_TABS_NO_BANNER = True

Expand Down Expand Up @@ -103,6 +110,7 @@ class ConfigurationViewer(Tabs, Traversable, Implicit):
{'label': 'Databases', 'action': '../Database/manage_main'},
{'label': 'Configuration', 'action': 'manage_main'},
{'label': 'DAV Locks', 'action': '../DavLocks/manage_main'},
{'label': 'Debug Information', 'action': '../DebugInfo/manage_main'},
)
MANAGE_TABS_NO_BANNER = True

Expand Down Expand Up @@ -133,7 +141,107 @@ def manage_getConfiguration(self):
InitializeClass(ConfigurationViewer)


class ApplicationManager(Persistent, Tabs, Traversable, Implicit):
# refcount snapshot info
_v_rcs = None
_v_rst = None


class DebugManager(Tabs, Traversable, Implicit):
""" Debug and profiling information
"""
manage = manage_main = manage_workspace = DTMLFile('dtml/debug', globals())
manage_main._setName('manage_main')
id = 'DebugInfo'
name = title = 'Debug Information'
meta_type = name
zmi_icon = 'fas fa-bug'

manage_options = (
{'label': 'Control Panel', 'action': '../manage_main'},
{'label': 'Databases', 'action': '../Database/manage_main'},
{'label': 'Configuration', 'action': '../Configuration/manage_main'},
{'label': 'DAV Locks', 'action': '../DAVLocks/manage_main'},
{'label': 'Debug Information', 'action': 'manage_main'},
)

def refcount(self, n=None, t=(type(Implicit),) + class_types):
# return class reference info
counts = {}
for m in list(sys.modules.values()):
if m is None:
continue
if not isinstance(m, types.ModuleType) or 'six.' in m.__name__:
continue
for sym in dir(m):
ob = getattr(m, sym)
if type(ob) in t:
counts[ob] = sys.getrefcount(ob)
pairs = []
for ob, v in counts.items():
ob_name = getattr(ob, "__name__", "unknown")
if hasattr(ob, '__module__'):
name = '%s.%s' % (ob.__module__, ob_name)
else:
name = '%s' % ob_name
pairs.append((v, name))
pairs.sort()
pairs.reverse()
if n is not None:
pairs = pairs[:n]
return pairs

def refdict(self):
counts = {}
for v, n in self.refcount():
counts[n] = v
return counts

def rcsnapshot(self):
global _v_rcs
global _v_rst
_v_rcs = self.refdict()
_v_rst = DateTime()

def rcdate(self):
return _v_rst

def rcdeltas(self):
if _v_rcs is None:
self.rcsnapshot()
nc = self.refdict()
rc = _v_rcs
rd = []
for n, c in nc.items():
try:
prev = rc.get(n, 0)
if c > prev:
rd.append((c - prev, (c, prev, n)))
except Exception:
pass
rd.sort()
rd.reverse()
return [{'name': n[1][2],
'delta': n[0],
'pc': n[1][1],
'rc': n[1][0],
} for n in rd]

def dbconnections(self):
import Zope2 # for data
return Zope2.DB.connectionDebugInfo()

def manage_getSysPath(self):
return list(sys.path)


InitializeClass(DebugManager)


class ApplicationManager(CacheManager,
Persistent,
Tabs,
Traversable,
Implicit):
"""System management
"""
__allow_access_to_unprotected_subobjects__ = 1
Expand All @@ -148,6 +256,7 @@ class ApplicationManager(Persistent, Tabs, Traversable, Implicit):
Database = DatabaseChooser()
Configuration = ConfigurationViewer()
DavLocks = DavLockManager()
DebugInfo = DebugManager()

manage = manage_main = DTMLFile('dtml/cpContents', globals())
manage_main._setName('manage_main')
Expand All @@ -156,6 +265,7 @@ class ApplicationManager(Persistent, Tabs, Traversable, Implicit):
{'label': 'Databases', 'action': 'Database/manage_main'},
{'label': 'Configuration', 'action': 'Configuration/manage_main'},
{'label': 'DAV Locks', 'action': 'DavLocks/manage_main'},
{'label': 'Debug Information', 'action': 'DebugInfo/manage_main'},
)
MANAGE_TABS_NO_BANNER = True

Expand Down Expand Up @@ -190,6 +300,9 @@ def sys_version(self):
def sys_platform(self):
return sys.platform

def thread_get_ident(self):
return get_ident()

def debug_mode(self):
return getConfiguration().debug_mode

Expand Down
86 changes: 86 additions & 0 deletions src/App/CacheManager.py
@@ -0,0 +1,86 @@
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Cache management support.
This class is mixed into the application manager in App.ApplicationManager.
"""

from AccessControl.class_init import InitializeClass
from Acquisition import aq_parent


class CacheManager:
"""Cache management mix-in
"""

def _getDB(self):
try:
return self._p_jar.db()
except AttributeError:
return aq_parent(self)._p_jar.db()

def cache_size(self):
db = self._getDB()
return db.getCacheSize()

def cache_detail(self, REQUEST=None):
"""
Returns the name of the classes of the objects in the cache
and the number of objects in the cache for each class.
"""
detail = self._getDB().cacheDetail()
if REQUEST is not None:
# format as text
REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain')
return '\n'.join(
['%6d %s' % (count, name) for name, count in detail])
# raw
return detail

def cache_extreme_detail(self, REQUEST=None):
"""
Returns information about each object in the cache.
"""
detail = self._getDB().cacheExtremeDetail()
if REQUEST is not None:
# sort the list.
lst = [((dict['conn_no'], dict['oid']), dict) for dict in detail]
# format as text.
res = [
'# Table shows connection number, oid, refcount, state, '
'and class.',
'# States: L = loaded, G = ghost, C = changed']
for sortkey, dict in lst:
id = dict.get('id', None)
if id:
idinfo = ' (%s)' % id
else:
idinfo = ''
s = dict['state']
if s == 0:
state = 'L' # loaded
elif s == 1:
state = 'C' # changed
else:
state = 'G' # ghost
res.append('%d %-34s %6d %s %s%s' % (
dict['conn_no'], repr(dict['oid']), dict['rc'],
state, dict['klass'], idinfo))
REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain')
return '\n'.join(res)
else:
# raw
return detail


InitializeClass(CacheManager)
10 changes: 8 additions & 2 deletions src/App/DavLockManager.py
Expand Up @@ -31,10 +31,16 @@ class DavLockManager(Item, Implicit):

security.declareProtected(webdav_manage_locks, # NOQA: D001
'manage_davlocks')
manage_davlocks = manage_main = manage = DTMLFile(
manage_davlocks = manage_main = manage = manage_workspace = DTMLFile(
'dtml/davLockManager', globals())
manage_davlocks._setName('manage_davlocks')
manage_options = ({'label': 'Write Locks', 'action': 'manage_main'}, )
manage_options = (
{'label': 'Control Panel', 'action': '../manage_main'},
{'label': 'Databases', 'action': 'manage_main'},
{'label': 'Configuration', 'action': '../Configuration/manage_main'},
{'label': 'DAV Locks', 'action': 'manage_main'},
{'label': 'Debug Information', 'action': '../DebugInfo/manage_main'},
)

@security.protected(webdav_manage_locks)
def findLockedObjects(self, frompath=''):
Expand Down
4 changes: 3 additions & 1 deletion src/App/dtml/davLockManager.dtml
@@ -1,5 +1,7 @@
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<dtml-with "_(management_view='DAV Locks')">
<dtml-var manage_tabs>
</dtml-with>

<main class="container-fluid">

Expand Down

0 comments on commit 111666e

Please sign in to comment.