[Zope-Checkins] CVS: Zope2 - RefreshFuncs.py:1.1 Product.py:1.49
shane@digicool.com
shane@digicool.com
Thu, 17 May 2001 14:35:39 -0400 (EDT)
Update of /cvs-repository/Zope2/lib/python/App
In directory korak.digicool.com:/tmp/cvs-serv11564/lib/python/App
Modified Files:
Product.py
Added Files:
RefreshFuncs.py
Log Message:
The Refresh product.
--- Added File RefreshFuncs.py in package Zope2 ---
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
'''
Functions for refreshing products.
$Id: RefreshFuncs.py,v 1.1 2001/05/17 18:35:08 shane Exp $
'''
import os, sys
from time import time
from string import split, join
import Products
from ExtensionClass import Base
from Globals import PersistentMapping
from zLOG import format_exception, LOG, ERROR, INFO
global_classes_timestamp = 0
products_mod_times = {}
_marker = [] # create a new marker object.
refresh_exc_info = {}
class dummyClass: pass
class dummyClass2 (Base): pass
def dummyFunc(): pass
ClassTypes = (type(dummyClass), type(dummyClass2))
ModuleType = type(sys)
FuncType = type(dummyFunc)
next_auto_refresh_check = 0
AUTO_REFRESH_INTERVAL = 2 # 2 seconds.
# Functions for storing and retrieving the auto-refresh state for
# each product.
def _getCentralRefreshData(jar, create=0):
root = jar.root()
if root.has_key('RefreshData'):
rd = root['RefreshData']
else:
rd = PersistentMapping()
if create:
root['RefreshData'] = rd
return rd
def isAutoRefreshEnabled(jar, productid):
rd = _getCentralRefreshData(jar)
ids = rd.get('auto', None)
if ids:
return ids.get(productid, 0)
else:
return 0
def enableAutoRefresh(jar, productid, enable):
productid = str(productid)
rd = _getCentralRefreshData(jar, 1)
ids = rd.get('auto', None)
if ids is None:
if enable:
rd['auto'] = ids = PersistentMapping()
else:
return
if enable:
ids[productid] = 1
else:
if ids.has_key(productid):
del ids[productid]
def listAutoRefreshableProducts(jar):
rd = _getCentralRefreshData(jar)
auto = rd.get('auto', None)
if auto:
ids = []
for k, v in auto.items():
if v:
ids.append(k)
return ids
else:
return ()
def getDependentProducts(jar, productid):
rd = _getCentralRefreshData(jar)
products = rd.get('products', None)
if products is None:
return ()
product = products.get(productid, None)
if product is None:
return ()
return product.get('dependent_products', ())
def setDependentProducts(jar, productid, dep_ids):
productid = str(productid)
rd = _getCentralRefreshData(jar, 1)
products = rd.get('products', None)
if products is None:
rd['products'] = products = PersistentMapping()
product = products.get(productid, None)
if product is None:
products[productid] = product = PersistentMapping()
product['dependent_products'] = tuple(map(str, dep_ids))
# Functions for sorting modules by dependency.
def listRequiredModulesByClass(klass, rval):
if hasattr(klass, '__module__'):
rval[klass.__module__] = 1 # klass.__module__ is a string.
if hasattr(klass, '__bases__'):
for b in klass.__bases__:
listRequiredModulesByClass(b, rval)
def listRequiredModules(module):
rval = {}
if hasattr(module, '__dict__'):
for key, value in module.__dict__.items():
t = type(value)
if t in ClassTypes:
listRequiredModulesByClass(value, rval)
elif t is ModuleType and hasattr(value, '__name__'):
rval[value.__name__] = 1
elif t is FuncType and value.func_globals.has_key('__name__'):
rval[value.func_globals['__name__']] = 1
return rval
def sortModulesByDependency(modlist):
unchosen = {}
for name, module in modlist:
unchosen[name] = (module, listRequiredModules(module))
chose = 1
rval = []
while chose:
chose = 0
for name, (module, req) in unchosen.items():
all_satisfied = 1
for n in unchosen.keys():
if name == n:
continue # Skip self.
if req.has_key(n):
# There is still a dependency. Can't
# include this module in the list yet.
all_satisfied = 0
break
if all_satisfied:
chose = 1
rval.append((name, module))
del unchosen[name]
# There might be some modules left over that are interdependent.
for name, (module, req) in unchosen.items():
rval.append((name, module))
return rval
# Functions for performing refresh.
def getReloadVar(module):
reload_var = getattr(module, '__refresh_module__', _marker)
if reload_var is _marker:
reload_var = getattr(module, '__reload_module__', _marker)
if reload_var is _marker:
reload_var = 1
return reload_var
def listRefreshableModules(productid):
prefix = "Products.%s" % productid
prefixdot = prefix + '.'
lpdot = len(prefixdot)
rval = []
for name, module in sys.modules.items():
if module and (name == prefix or name[:lpdot] == prefixdot):
reload_var = getReloadVar(module)
if callable(reload_var) or reload_var:
rval.append((name, module))
return rval
def logBadRefresh(productid):
exc = sys.exc_info()
try:
LOG('Refresh', ERROR, 'Exception while refreshing %s'
% productid, error=exc)
if hasattr(exc[0], '__name__'):
error_type = exc[0].__name__
else:
error_type = str(exc[0])
error_value = str(exc[1])
info = format_exception(exc[0], exc[1], exc[2], limit=200)
refresh_exc_info[productid] = (error_type, error_value, info)
finally:
exc = None
def performRefresh(jar, productid):
'''Attempts to perform a refresh operation.
'''
refresh_exc_info[productid] = None
setupModTimes(productid) # Refresh again only if changed again.
modlist = listRefreshableModules(productid)
modlist = sortModulesByDependency(modlist)
for name, module in modlist:
# Remove the __import_error__ attribute.
try: del module.__import_error__
except: pass
# Ask the module how it should be reloaded.
reload_var = getReloadVar(module)
if callable(reload_var):
try:
reload_var()
except:
logBadRefresh(productid)
return 0
else:
try:
reload(module)
except:
logBadRefresh(productid)
return 0
# Reinitialize and reinstall the product.
from OFS import Application
Application.reimport_product(productid)
app = jar.root()['Application']
Application.reinstall_product(app, productid)
return 1
def performSafeRefresh(jar, productid):
try:
LOG('Refresh', INFO, 'Refreshing product %s' % productid)
if not performRefresh(jar, productid):
return 0
except:
logBadRefresh(productid)
return 0
else:
return 1
def performFullRefresh(jar, productid):
if performSafeRefresh(jar, productid):
dep_ids = getDependentProducts(jar, productid)
for dep_id in dep_ids:
if isAutoRefreshEnabled(jar, dep_id):
if not performSafeRefresh(jar, dep_id):
return 0
else:
return 0
return 1
def getLastRefreshException(productid):
return refresh_exc_info.get(productid, None)
# Functions for quickly scanning the dates of product modules.
def tryFindProductDirectory(productid):
path_join = os.path.join
isdir = os.path.isdir
exists = os.path.exists
for products_dir in Products.__path__:
product_dir = path_join(products_dir, productid)
if not isdir(product_dir): continue
if not exists(path_join(product_dir, '__init__.py')):
if not exists(path_join(product_dir, '__init__.pyc')):
continue
return product_dir
return None
def tryFindModuleFilename(product_dir, filename):
# Try different variations of the filename of a module.
path_join = os.path.join
isdir = os.path.isdir
exists = os.path.exists
found = None
fn = path_join(product_dir, filename + '.py')
if exists(fn):
found = fn
if not found:
fn = fn + 'c'
if exists(fn):
found = fn
if not found:
fn = path_join(product_dir, filename)
if isdir(fn):
fn = path_join(fn, '__init__.py')
if exists(fn):
found = fn
else:
fn = fn + 'c'
if exists(fn):
found = fn
return found
def setupModTimes(productid):
mod_times = []
product_dir = tryFindProductDirectory(productid)
if product_dir is not None:
modlist = listRefreshableModules(productid)
path_join = os.path.join
exists = os.path.exists
for name, module in modlist:
splitname = split(name, '.')[2:]
if not splitname:
filename = '__init__'
else:
filename = apply(path_join, splitname)
found = tryFindModuleFilename(product_dir, filename)
if found:
try: mtime = os.stat(found)[8]
except: mtime = 0
mod_times.append((found, mtime))
products_mod_times[productid] = mod_times
def checkModTimes(productid):
# Returns 1 if there were changes.
mod_times = products_mod_times.get(productid, None)
if mod_times is None:
# Initialize the mod times.
setupModTimes(productid)
return 0
for filename, mod_time in mod_times:
try: mtime = os.stat(filename)[8]
except: mtime = 0
if mtime != mod_time:
# Something changed!
return 1
return 0
# Functions for performing auto-refresh.
def checkAutoRefresh(jar):
# Note: this function is NOT allowed to change the database!
global next_auto_refresh_check
now = time()
if next_auto_refresh_check and next_auto_refresh_check > now:
# Not enough time has passed.
return ()
next_auto_refresh_check = now + AUTO_REFRESH_INTERVAL
rd = _getCentralRefreshData(jar)
ids = rd.get('auto', None)
if not ids:
return ()
auto_refresh_ids = []
for productid in ids.keys():
if checkModTimes(productid):
auto_refresh_ids.append(productid)
return auto_refresh_ids
def finishAutoRefresh(jar, productids):
# This function is allowed to change the database.
for productid in productids:
performFullRefresh(jar, productid)
def autoRefresh(jar):
# Must be called before there are any changes made
# by the connection to the database!
auto_refresh_ids = checkAutoRefresh(jar)
if auto_refresh_ids:
finishAutoRefresh(jar, auto_refresh_ids)
from ZODB import Connection
Connection.updateCodeTimestamp()
get_transaction().commit()
jar._resetCache()
get_transaction().begin()
def setupAutoRefresh(jar):
# Install hook.
from ZODB.ZApplication import connection_open_hooks
connection_open_hooks.append(autoRefresh)
# Init mod times.
checkAutoRefresh(jar)
--- Updated File Product.py in package Zope2 --
--- Product.py 2001/04/27 18:07:08 1.48
+++ Product.py 2001/05/17 18:35:08 1.49
@@ -116,6 +116,7 @@
from Permission import PermissionManager
import ZClasses, ZClasses.ZClass
from HelpSys.HelpSys import ProductHelp
+import RefreshFuncs
class ProductFolder(Folder):
@@ -341,6 +342,129 @@
self._setObject('Help', ProductHelp('Help', self.id))
return self.Help
+ #
+ # Product refresh
+ #
+
+ _refresh_dtml = Globals.DTMLFile('dtml/refresh', globals())
+
+ def _readRefreshTxt(self, pid=None):
+ refresh_txt = None
+ if pid is None:
+ pid = self.id
+ for productDir in Products.__path__:
+ found = 0
+ for name in ('refresh.txt', 'REFRESH.txt', 'REFRESH.TXT'):
+ p = os.path.join(productDir, pid, name)
+ if os.path.exists(p):
+ found = 1
+ break
+ if found:
+ try:
+ file = open(p)
+ text = file.read()
+ file.close()
+ refresh_txt = text
+ break
+ except:
+ # Not found here.
+ pass
+ return refresh_txt
+
+ def manage_refresh(self, REQUEST, manage_tabs_message=None):
+ '''
+ Displays the refresh management screen.
+ '''
+ error_type = error_value = error_tb = None
+ exc = RefreshFuncs.getLastRefreshException(self.id)
+ if exc is not None:
+ error_type, error_value, error_tb = exc
+ exc = None
+
+ refresh_txt = self._readRefreshTxt()
+
+ # Read the persistent refresh information.
+ auto = RefreshFuncs.isAutoRefreshEnabled(self._p_jar, self.id)
+ deps = RefreshFuncs.getDependentProducts(self._p_jar, self.id)
+
+ # List all product modules.
+ mods = RefreshFuncs.listRefreshableModules(self.id)
+ loaded_modules = []
+ prefix = 'Products.%s' % self.id
+ prefixdot = prefix + '.'
+ lpdot = len(prefixdot)
+ for name, module in mods:
+ if name == prefix or name[:lpdot] == prefixdot:
+ name = name[lpdot:]
+ if not name:
+ name = '__init__'
+ loaded_modules.append(name)
+
+ all_auto = RefreshFuncs.listAutoRefreshableProducts(self._p_jar)
+ for pid in all_auto:
+ # Ignore products that don't have a refresh.txt.
+ if self._readRefreshTxt(pid) is None:
+ all_auto.remove(pid)
+ auto_other = filter(lambda productId, myId=self.id:
+ productId != myId, all_auto)
+
+ # Return rendered DTML.
+ return self._refresh_dtml(REQUEST,
+ id=self.id,
+ refresh_txt=refresh_txt,
+ error_type=error_type,
+ error_value=error_value,
+ error_tb=error_tb,
+ devel_mode=Globals.DevelopmentMode,
+ auto_refresh_enabled=auto,
+ auto_refresh_other=auto_other,
+ dependent_products=deps,
+ loaded_modules=loaded_modules,
+ manage_tabs_message=manage_tabs_message,
+ management_view='Refresh')
+
+ def manage_performRefresh(self, REQUEST=None):
+ '''
+ Attempts to perform a refresh operation.
+ '''
+ if self._readRefreshTxt() is None:
+ raise 'Unauthorized', 'refresh.txt not found'
+ message = None
+ if RefreshFuncs.performFullRefresh(self._p_jar, self.id):
+ from ZODB import Connection
+ Connection.updateCodeTimestamp() # Clears cache in next connection.
+ message = 'Product refreshed.'
+ else:
+ message = 'An exception occurred.'
+ if REQUEST is not None:
+ return self.manage_refresh(REQUEST, manage_tabs_message=message)
+
+ def manage_enableAutoRefresh(self, enable=0, REQUEST=None):
+ '''
+ Changes the auto refresh flag for this product.
+ '''
+ if self._readRefreshTxt() is None:
+ raise 'Unauthorized', 'refresh.txt not created'
+ RefreshFuncs.enableAutoRefresh(self._p_jar, self.id, enable)
+ if enable:
+ message = 'Enabled auto refresh.'
+ else:
+ message = 'Disabled auto refresh.'
+ if REQUEST is not None:
+ return self.manage_refresh(REQUEST, manage_tabs_message=message)
+
+ def manage_selectDependentProducts(self, selections=(), REQUEST=None):
+ '''
+ Selects which products to refresh simultaneously.
+ '''
+ if self._readRefreshTxt() is None:
+ raise 'Unauthorized', 'refresh.txt not created'
+ RefreshFuncs.setDependentProducts(self._p_jar, self.id, selections)
+ if REQUEST is not None:
+ return self.manage_refresh(REQUEST)
+
+
+
class CompressedOutputFile:
def __init__(self, rot):
self._c=zlib.compressobj()
@@ -476,6 +600,17 @@
{'label':'README', 'action':'manage_readme'},
)
break
+
+ # Ensure this product has a refresh tab.
+ found = 0
+ for option in product.manage_options:
+ if option.get('label') == 'Refresh':
+ found = 1
+ break
+ if not found:
+ product.manage_options = product.manage_options + (
+ {'label':'Refresh', 'action':'manage_refresh',
+ 'help': ('OFSP','Product_Refresh.stx')},)
if (os.environ.get('ZEO_CLIENT') and
not os.environ.get('FORCE_PRODUCT_LOAD')):