Commit d8eaafb3 authored by Shane Hathaway's avatar Shane Hathaway

The Refresh product.

parent 1e9da134
......@@ -57,6 +57,10 @@ Zope changes
- DTML-In and DTML-Tree now have optional "prefix" attributes
that can be used to make friendlier tag variable names.
- Added product reloading capability, formerly provided by
the "Refresh" product. This enables developers to see the
effect of changes to their products without restarting Zope.
Zope 2.3.0 beta 3
Features Added
......
......@@ -116,6 +116,7 @@ from Factory import Factory
from Permission import PermissionManager
import ZClasses, ZClasses.ZClass
from HelpSys.HelpSys import ProductHelp
import RefreshFuncs
class ProductFolder(Folder):
......@@ -341,6 +342,129 @@ class Product(Folder, PermissionManager):
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()
......@@ -477,6 +601,17 @@ def initializeProduct(productp, name, home, app):
)
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')):
get_transaction().abort()
......
##############################################################################
#
# 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)
<dtml-comment>
Arguments for this method:
id, refresh_txt, error_type, error_value, error_tb, devel_mode,
auto_refresh_enabled, auto_refresh_other, dependent_products,
loaded_modules
</dtml-comment>
<dtml-let form_title="'Refresh product: ' + id">
<dtml-if manage_page_header>
<dtml-var manage_page_header>
<dtml-else>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html lang="en">
<head>
<title>&dtml-form_title;</title>
</head>
<body bgcolor="#FFFFFF" link="#000099" vlink="#555555">
<h3>&dtml-form_title;</h3>
</dtml-if>
</dtml-let>
<dtml-var manage_tabs>
<dtml-if expr="refresh_txt == _.None">
<p>The refresh function, designed to ease the development of Zope
products, is not currently enabled for this product.
To make it available, put a file named "refresh.txt" in the &dtml-id;
product directory. Please note that not all products are
compatible with the refresh function.</p>
<dtml-else>
<dtml-if error_type>
<p><b>An exception occurred during the last refresh.</b><br />
Exception type: <b>&dtml-error_type;</b> <br />
Exception value: <b>&dtml-error_value;</b>
</p>
<pre>&dtml-error_tb;</pre>
<hr />
</dtml-if>
<form action="&dtml-absolute_url;" method="POST">
<table border="0">
<tr>
<td valign="top">
<dtml-if expr="_.string.strip(refresh_txt)">
<p>
<b>Important information about refreshing this product:</b><br />
<dtml-var refresh_txt fmt="structured-text">
</p>
</dtml-if>
<div align="center"><input type="submit"
name="manage_performRefresh:method" value="Refresh this product" />
</div>
<p>
<dtml-if auto_refresh_enabled>
<dtml-if devel_mode>
Auto refresh is enabled. Zope will repeatedly scan for
changes to the Python modules that make up this product and
execute a refresh when needed.
<dtml-else>
Although auto refresh is enabled, Zope is not in development
mode so auto refresh is not available. Use the "-D" argument
when starting Zope to enable development mode.
</dtml-if>
<dtml-else>
Auto refresh is disabled. Enable auto refresh
to cause Zope to frequently scan this product for changes.
Note that auto refresh can slow down Zope considerably
if enabled for more than a few products.
</dtml-if>
<br />
<dtml-let checked="auto_refresh_enabled and 'checked' or ' '">
<input type="checkbox" name="enable" value="1" &dtml-checked; />
Auto refresh mode &nbsp;
<input type="submit" name="manage_enableAutoRefresh:method"
value="Change" />
</dtml-let>
</p>
<dtml-if auto_refresh_other>
<p>Select dependent auto-refreshable products to be refreshed
simultaneously.<br />
<dtml-in auto_refresh_other sort>
<dtml-let checked="(_['sequence-item'] in dependent_products) and
'checked' or ' '">
<input type="checkbox" name="selections:list"
value="&dtml-sequence-item;" &dtml-checked; />
<a href="../&dtml-sequence-item;/manage_refresh"
>&dtml-sequence-item;</a><br />
</dtml-let>
</dtml-in>
<input type="submit" name="manage_selectDependentProducts:method"
value="Change" />
</p>
</dtml-if>
</td>
<td valign="top" class="row-hilite">
<p><b>Refreshable product modules:</b></p>
<ul>
<dtml-in loaded_modules sort>
<li>&dtml-sequence-item;</li>
</dtml-in>
</ul>
</td>
</tr>
</table>
</form>
</dtml-if>
<dtml-if manage_page_footer>
<dtml-var manage_page_footer>
<dtml-else>
</body></html>
</dtml-if>
......@@ -85,8 +85,8 @@
__doc__='''Application support
$Id: Application.py,v 1.143 2001/04/07 16:37:07 jim Exp $'''
__version__='$Revision: 1.143 $'[11:-2]
$Id: Application.py,v 1.144 2001/05/17 18:35:09 shane Exp $'''
__version__='$Revision: 1.144 $'[11:-2]
import Globals,Folder,os,sys,App.Product, App.ProductRegistry, misc_
import time, traceback, os, string, Products
......@@ -495,16 +495,8 @@ def initialize(app):
get_transaction().abort()
def import_products(_st=type('')):
def import_products():
# Try to import each product, checking for and catching errors.
path_join=os.path.join
isdir=os.path.isdir
exists=os.path.exists
DictType=type({})
global_dict=globals()
silly=('__doc__',)
modules=sys.modules
have_module=modules.has_key
done={}
for product_dir in Products.__path__:
......@@ -516,12 +508,26 @@ def import_products(_st=type('')):
if done.has_key(product_name): continue
done[product_name]=1
import_product(product_dir, product_name)
def import_product(product_dir, product_name, raise_exc=0, log_exc=1):
path_join=os.path.join
isdir=os.path.isdir
exists=os.path.exists
_st=type('')
global_dict=globals()
silly=('__doc__',)
modules=sys.modules
have_module=modules.has_key
if 1: # Preserve indentation for diff :-)
try:
package_dir=path_join(product_dir, product_name)
if not isdir(package_dir): continue
if not isdir(package_dir): return
if not exists(path_join(package_dir, '__init__.py')):
if not exists(path_join(package_dir, '__init__.pyc')):
continue
return
pname="Products.%s" % product_name
try:
......@@ -532,35 +538,27 @@ def import_products(_st=type('')):
if type(v) is _st and have_module(v): v=modules[v]
modules[k]=v
except:
LOG('Zope',ERROR,'Couldn\'t import %s' % pname,
error=sys.exc_info())
exc = sys.exc_info()
if log_exc:
LOG('Zope', ERROR, 'Could not import %s' % pname,
error=exc)
f=StringIO()
traceback.print_exc(100,f)
f=f.getvalue()
try: modules[pname].__import_error__=f
except: pass
if raise_exc:
raise exc[0], exc[1], exc[2]
finally:
exc = None
def install_products(app):
# Install a list of products into the basic folder class, so
# that all folders know about top-level objects, aka products
path_join=os.path.join
isdir=os.path.isdir
exists=os.path.exists
DictType=type({})
from Folder import Folder
folder_permissions={}
for p in Folder.__ac_permissions__:
permission, names = p[:2]
folder_permissions[permission]=names
folder_permissions = get_folder_permissions()
meta_types=[]
global_dict=globals()
silly=('__doc__',)
done={}
get_transaction().note('Prior to product installs')
......@@ -579,13 +577,38 @@ def install_products(app):
if done.has_key(product_name):
continue
done[product_name]=1
install_product(app, product_dir, product_name, meta_types,
folder_permissions)
Products.meta_types=Products.meta_types+tuple(meta_types)
Globals.default__class_init__(Folder.Folder)
def get_folder_permissions():
folder_permissions={}
for p in Folder.Folder.__ac_permissions__:
permission, names = p[:2]
folder_permissions[permission]=names
return folder_permissions
def install_product(app, product_dir, product_name, meta_types,
folder_permissions, raise_exc=0, log_exc=1):
path_join=os.path.join
isdir=os.path.isdir
exists=os.path.exists
DictType=type({})
global_dict=globals()
silly=('__doc__',)
if 1: # Preserve indentation for diff :-)
package_dir=path_join(product_dir, product_name)
__traceback_info__=product_name
if not isdir(package_dir): continue
if not isdir(package_dir): return
if not exists(path_join(package_dir, '__init__.py')):
if not exists(path_join(package_dir, '__init__.pyc')):
continue
return
try:
product=__import__("Products.%s" % product_name,
global_dict, global_dict, silly)
......@@ -605,7 +628,7 @@ def install_products(app):
# should be associated with that product. Products are
# expected to implement a method named 'initialize' in
# their __init__.py that takes the ProductContext as an
# argument.
# argument.
productObject=App.Product.initializeProduct(
product, product_name, package_dir, app)
context=ProductContext(productObject, app, product)
......@@ -616,7 +639,7 @@ def install_products(app):
# build up enough information to do initialization manually.
initmethod=pgetattr(product, 'initialize', None)
if initmethod is not None:
initmethod(context)
initmethod(context)
# Support old-style product metadata. Older products may
# define attributes to name their permissions, meta_types,
......@@ -661,8 +684,8 @@ def install_products(app):
for permission, names in new_permissions:
folder_permissions[permission]=names
new_permissions.sort()
Folder.__dict__['__ac_permissions__']=tuple(
list(Folder.__ac_permissions__)+new_permissions)
Folder.Folder.__dict__['__ac_permissions__']=tuple(
list(Folder.Folder.__ac_permissions__)+new_permissions)
if (os.environ.get('ZEO_CLIENT') and
not os.environ.get('FORCE_PRODUCT_LOAD')):
......@@ -674,13 +697,58 @@ def install_products(app):
get_transaction().commit()
except:
LOG('Zope',ERROR,'Couldn\'t install %s' % product_name,
error=sys.exc_info())
if log_exc:
LOG('Zope',ERROR,'Couldn\'t install %s' % product_name,
error=sys.exc_info())
get_transaction().abort()
if raise_exc:
raise
def reinstall_product(app, product_name):
folder_permissions = get_folder_permissions()
meta_types=[]
get_transaction().note('Prior to product reinstall')
get_transaction().commit()
for product_dir in Products.__path__:
product_names=os.listdir(product_dir)
product_names.sort()
if product_name in product_names:
removeProductMetaTypes(product_name)
install_product(app, product_dir, product_name, meta_types,
folder_permissions, raise_exc=1, log_exc=0)
Products.meta_types=Products.meta_types+tuple(meta_types)
Globals.default__class_init__(Folder.Folder)
Globals.default__class_init__(Folder)
def reimport_product(product_name):
for product_dir in Products.__path__:
product_names=os.listdir(product_dir)
product_names.sort()
if product_name in product_names:
import_product(product_dir, product_name,
raise_exc=1, log_exc=0)
break
def removeProductMetaTypes(pid):
'''
Unregisters the meta types registered by a product.
'''
meta_types = Products.meta_types
new_mts = []
changed = 0
for meta_type in meta_types:
if meta_type.get('product', None) == pid:
# Remove this meta type.
changed = 1
else:
new_mts.append(meta_type)
if changed:
Products.meta_types = tuple(new_mts)
def pgetattr(product, name, default=install_products, __init__=0):
......
Product - Refresh: Reload a filesystem-based Python product.
Description
This view allows you to reload filesystem-based product code
without restarting Zope. This function is useful during
development of products.
To enable your product to be refreshed, it is required that you
put a file called 'refresh.txt' in your product directory.
It can optionally contain a warning for others who might use
the refresh function.
(Producting refreshing is not perfect. Some products, especially
"hotfix" style products which patch Zope, should not be refreshed.
That's why 'refresh.txt' is required. Most products are safe to
refresh, however.)
There are two modes of operation. You can visit your product's
'Refresh' tab and manually push the refresh button. Or you can
turn on "auto-refresh" mode, which causes Zope to periodically
scan the modification time of the Python files that make up your
product and execute a refresh operation in the background.
**NOTE**: Don't enable auto-refresh for too many products at once.
Scanning file modification times can take a lot of time per
request.
You can also select dependent refreshable products. If you have
a product that subclasses from a product you're working on,
you'll want to enable refresh for both products and add the
product that subclasses as a dependent of the product you're
modifying. This enables subclasses to be updated.
Controls
'Refresh this product' -- The manual refresh button.
'Auto refresh mode' -- Check the checkbox to enable auto-refresh.
'Dependent auto-refreshable products' -- A list of other products
which are auto-refreshable.
How it works
To execute a refresh, Zope looks in the sys.modules dictionary
for modules with names that start with the prefix for your product.
It tries to scan for dependencies between the modules that make
up your product then uses Python's reload() function for each
module in order. Then it sets a flag that will cause ZODB to dump
its cache on the next connection so that changes to persistent
classes will take effect.
To implement auto-refresh, Zope stores a PersistentMapping called
RefreshData on the database root object (below the Application
object). The contents of the PersistentMapping are examined at the
moment a database connection is opened by ZApplication. The
PersistentMapping contains a list of which products have auto-refresh
enabled. For each product with auto-refresh enabled, Zope compares
the file mod times with the last recorded times and executes a
refresh if there are any changes.
......@@ -142,13 +142,18 @@ app=bobo_application=ZODB.ZApplication.ZApplicationWrapper(
# Initialize products:
c=app()
OFS.Application.initialize(c)
if Globals.DevelopmentMode:
# Set up auto-refresh.
from App.RefreshFuncs import setupAutoRefresh
setupAutoRefresh(c._p_jar)
c._p_jar.close()
del c
# "Log off" as system user
AccessControl.SecurityManagement.noSecurityManager()
# This is sneaky, but we don't want to play with Main:
sys.modules['Main']=sys.modules['Zope']
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment