############################################################################## # # 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. # ############################################################################## """Implement a client cache The cache is managed as two files, var/c0.zec and var/c1.zec. Each cache file is a sequence of records of the form: oid -- 8-byte object id status -- 1-byte status v': valid, 'n': non-version valid, 'i': invalid tlen -- 4-byte (unsigned) record length vlen -- 2-bute (unsigned) version length dlen -- 4-byte length of non-version data serial -- 8-byte non-version serial (timestamp) data -- non-version data version -- Version string (if vlen > 0) vdlen -- 4-byte length of version data (if vlen > 0) vdata -- version data (if vlen > 0) vserial -- 8-byte version serial (timestamp) tlen -- 4-byte (unsigned) record length (for redundancy and backward traversal) There is a cache size limit. The cache is managed as follows: - Data are written to file 0 until file 0 exceeds limit/2 in size. - Data are written to file 1 until file 1 exceeds limit/2 in size. - File 0 is truncated to size 0 (or deleted and recreated). - Data are written to file 0 until file 0 exceeds limit/2 in size. - File 1 is truncated to size 0 (or deleted and recreated). - Data are written to file 1 until file 1 exceeds limit/2 in size. and so on. On startup, index information is read from file 0 and file 1. Current serial numbers are sent to the server for verification. If any serial numbers are not valid, then the server will send back invalidation messages and the cache entries will be invalidated. When a cache record is invalidated, the data length is overwritten with '\0\0\0\0'. If var is not writable, then temporary files are used for file 0 and file 1. """ __version__ = "$Revision: 1.5 $"[11:-2] import os, tempfile from struct import pack, unpack magic='ZEC0' class ClientCache: def __init__(self, storage='', size=20000000, client=None, var=None): if client: if var is None: var=os.path.join(INSTANCE_HOME,'var') self._p=p=map(lambda i, p=storage, var=var, c=client: os.path.join(var,'c%s-%s-%s.zec' % (p, c, i)), (0,1)) self._f=f=[None, None] s=['\0\0\0\0\0\0\0\0', '\0\0\0\0\0\0\0\0'] for i in 0,1: if os.path.exists(p[i]): fi=open(p[i],'r+b') if fi.read(4)==magic: # Minimal sanity fi.seek(0,2) if fi.tell() > 30: fi.seek(22) s[i]=fi.read(8) if s[i]!='\0\0\0\0\0\0\0\0': f[i]=fi fi=None if s[1] > s[0]: current=1 elif s[0] > s[1]: current=0 else: if f[0] is None: f[0]=open(p[0], 'w+b') f[0].write(magic) current=0 f[1]=None else: self._p=p=map( lambda i, p=storage: tempfile.mktemp('.zec'), (0,1)) self._f=f=[open(p[0],'w+b'), None] f[0].write(magic) current=0 self._limit=size/2 self._current=current def open(self): self._index=index={} self._get=index.get serial={} f=self._f current=self._current if f[not current] is not None: read_index(index, serial, f[not current], not current) self._pos=read_index(index, serial, f[current], current) return serial.items() def invalidate(self, oid, version): p=self._get(oid, None) if p is None: return None f=self._f[p < 0] ap=abs(p) f.seek(ap) h=f.read(8) if h != oid: return f.write(version and 'n' or 'i') def load(self, oid, version): p=self._get(oid, None) if p is None: return None f=self._f[p < 0] ap=abs(p) seek=f.seek read=f.read seek(ap) h=read(27) if len(h)==27 and h[8] in 'nv' and h[:8]==oid: tlen, vlen, dlen = unpack(">iHi", h[9:19]) else: tlen=-1 if tlen <= 0 or vlen < 0 or dlen <= 0 or vlen+dlen > tlen: del self._index[oid] return None if version and h[8]=='n': return None if not vlen or not version: return read(dlen), h[19:] seek(dlen, 1) v=read(vlen) if version != v: seek(-dlen-vlen, 1) return read(dlen), h[19:] dlen=unpack(">i", read(4))[0] return read(dlen), read(8) def update(self, oid, serial, version, data): if version: # We need to find and include non-version data p=self._get(oid, None) if p is None: return None f=self._f[p < 0] ap=abs(p) seek=f.seek read=f.read seek(ap) h=read(27) if len(h)==27 and h[8] in 'nv' and h[:8]==oid: tlen, vlen, dlen = unpack(">iHi", h[9:19]) else: tlen=-1 if tlen <= 0 or vlen < 0 or dlen <= 0 or vlen+dlen > tlen: del self._index[oid] return None p=read(dlen) s=h[19:] self.store(oid, p, s, version, data, serial) else: # Simple case, just store new data: self.store(oid, data, serial, '', None, None) def modifiedInVersion(self, oid): p=self._get(oid, None) if p is None: return None f=self._f[p < 0] ap=abs(p) seek=f.seek read=f.read seek(ap) h=read(27) if len(h)==27 and h[8] in 'nv' and h[:8]==oid: tlen, vlen, dlen = unpack(">iHi", h[9:19]) else: tlen=-1 if tlen <= 0 or vlen < 0 or dlen <= 0 or vlen+dlen > tlen: del self._index[oid] return None if h[8]=='n': return None if not vlen: return '' seek(dlen, 1) return read(vlen) def store(self, oid, p, s, version, pv, sv): tlen=31+len(p) if version: tlen=tlen+len(version)+12+len(pv) vlen=len(version) else: vlen=0 pos=self._pos current=self._current if pos+tlen > self._limit: current=not current self._current=current self._f[current]=open(self._p[current],'w+b') self._f[current].write(magic) self._pos=pos=4 f=self._f[current] f.seek(pos) stlen=pack(">I",tlen) write=f.write write(oid+'v'+stlen+pack(">HI", vlen, len(p))+s) write(p) if version: write(pack(">I", len(pv))) write(pv) write(sv+stlen) if current: self._index[oid]=-pos else: self._index[oid]=pos self._pos=pos+tlen def read_index(index, serial, f, current): seek=f.seek read=f.read pos=4 seek(0,2) size=f.tell() while 1: f.seek(pos) h=read(27) if len(h)==27 and h[8] in 'vni': tlen, vlen, dlen = unpack(">iHi", h[9:19]) else: tlen=-1 if tlen <= 0 or vlen < 0 or dlen <= 0 or vlen+dlen > tlen: break oid=h[:8] if h[8]=='v' and vlen: seek(dlen+vlen, 1) vdlen=read(4) if len(vdlen) != 4: break vdlen=unpack(">i", vdlen)[0] if vlen+dlen+42+vdlen > tlen: break seek(vdlen, 1) vs=read(8) if read(4) != h[9:13]: break else: vs=None if h[8] in 'vn': if current: index[oid]=-pos else: index[oid]=pos serial[oid]=h[-8:], vs else: if serial.has_key(oid): # We has a record for this oid, but it was invalidated! del serial[oid] del index[oid] pos=pos+tlen f.seek(pos) try: f.truncate() except: pass return pos