[Zope3-checkins] CVS: Zope3/src/zodb - __init__.py:1.1.2.1 _timestamp.c:1.1.2.1 config.py:1.1.2.1 conflict.py:1.1.2.1 connection.py:1.1.2.1 db.py:1.1.2.1 dbdump.py:1.1.2.1 export.py:1.1.2.1 interfaces.py:1.1.2.1 lockfile.py:1.1.2.1 serialize.py:1.1.2.1 timestamp.py:1.1.2.1 utils.py:1.1.2.1 winlock.c:1.1.2.1 ztransaction.py:1.1.2.1

Jim Fulton jim@zope.com
Mon, 23 Dec 2002 14:30:46 -0500


Update of /cvs-repository/Zope3/src/zodb
In directory cvs.zope.org:/tmp/cvs-serv19908/zodb

Added Files:
      Tag: NameGeddon-branch
	__init__.py _timestamp.c config.py conflict.py connection.py 
	db.py dbdump.py export.py interfaces.py lockfile.py 
	serialize.py timestamp.py utils.py winlock.c ztransaction.py 
Log Message:
Initial renaming before debugging

=== Added File Zope3/src/zodb/__init__.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
# 
##############################################################################



=== Added File Zope3/src/zodb/_timestamp.c ===
/*****************************************************************************

  Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
  
  This software is subject to the provisions of the Zope Public License,
  Version 2.0 (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
  
 ****************************************************************************/

#include "Python.h"
#include <time.h>

PyObject *TimeStamp_FromDate(int, int, int, int, int, double);
PyObject *TimeStamp_FromString(const char *);

static char TimeStampModule_doc[] = 
"A 64-bit TimeStamp used as a ZODB serial number.\n";

typedef struct {
    PyObject_HEAD
    unsigned char data[8];
} TimeStamp;

/* The first dimension of the arrays below is non-leapyear / leapyear */

static char month_len[2][12]={
  {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, 
  {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

static short joff[2][12] = {
  {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
  {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
};

static double gmoff=0;

/* XXX should this be stored in sconv? */
#define SCONV ((double)60) / ((double)(1<<16)) / ((double)(1<<16))

static int
leap(int year)
{
    return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}

static int
days_in_month(int year, int month)
{
    return month_len[leap(year)][month];
}

static double
TimeStamp_yad(int y)
{
    double d, s;

    y -= 1900;
  
    d = (y - 1) * 365;
    if (y > 0) {
        s = 1.0;
	y -= 1;
    } else {
	s = -1.0;
	y = -y;
    }
    return d + s * (y / 4 - y / 100 + (y + 300) / 400);
}

static double
TimeStamp_abst(int y, int mo, int d, int m, int s)
{
    return (TimeStamp_yad(y) + joff[leap(y)][mo] + d) * 86400 + m * 60 + s;
}

static int
TimeStamp_init_gmoff(void)
{
    struct tm *t;
    time_t z=0;
  
    t = gmtime(&z);
    if (t == NULL) {
	PyErr_SetString(PyExc_SystemError, "gmtime failed");
	return -1;
    }

    gmoff = TimeStamp_abst(t->tm_year+1900, t->tm_mon, t->tm_mday - 1, 
			   t->tm_hour * 60 + t->tm_min, t->tm_sec);

    return 0;
}

static void
TimeStamp_dealloc(TimeStamp *ts)
{
    PyObject_Del(ts);
}

static int
TimeStamp_compare(TimeStamp *v, TimeStamp *w)
{
    int cmp = memcmp(v->data, w->data, 8);
    if (cmp < 0) return -1;
    if (cmp > 0) return 1;
    return 0;
}

static long
TimeStamp_hash(TimeStamp *self)
{
    register unsigned char *p = (unsigned char *)self->data;
    register int len = 8;
    register long x = *p << 7;
    /* XXX unroll loop? */
    while (--len >= 0)
	x = (1000003*x) ^ *p++;
    x ^= 8;
    if (x == -1)
	x = -2;
    return x;
}

typedef struct {
    int y, m, d, mi;
} TimeStampParts;
  
static void 
TimeStamp_unpack(TimeStamp *self, TimeStampParts *p)
{
    unsigned long v;

    v = (self->data[0] * 16777216 + self->data[1] * 65536 
	 + self->data[2] * 256 + self->data[3]);
    p->y = v / 535680 + 1900;
    p->m = (v % 535680) / 44640 + 1;
    p->d = (v % 44640) / 1440 + 1;
    p->mi = v % 1440;
}

static double
TimeStamp_sec(TimeStamp *self)
{
    unsigned int v;

    v = (self->data[4] * 16777216 + self->data[5] * 65536
	 + self->data[6] * 256 + self->data[7]);
    return SCONV * v;
}

static PyObject *
TimeStamp_year(TimeStamp *self)
{
    TimeStampParts p;
    TimeStamp_unpack(self, &p);
    return PyInt_FromLong(p.y);
}

static PyObject *
TimeStamp_month(TimeStamp *self)
{
    TimeStampParts p;
    TimeStamp_unpack(self, &p);
    return PyInt_FromLong(p.m);
}

static PyObject *
TimeStamp_day(TimeStamp *self)
{
    TimeStampParts p;
    TimeStamp_unpack(self, &p);
    return PyInt_FromLong(p.d);
}

static PyObject *
TimeStamp_hour(TimeStamp *self)
{
    TimeStampParts p;
    TimeStamp_unpack(self, &p);
    return PyInt_FromLong(p.mi / 60);
}

static PyObject *
TimeStamp_minute(TimeStamp *self)
{
    TimeStampParts p;
    TimeStamp_unpack(self, &p);
    return PyInt_FromLong(p.mi % 60);
}

static PyObject *
TimeStamp_second(TimeStamp *self)
{
    return PyFloat_FromDouble(TimeStamp_sec(self));
}

static PyObject *
TimeStamp_timeTime(TimeStamp *self)
{
    TimeStampParts p;
    TimeStamp_unpack(self, &p);
    return PyFloat_FromDouble(TimeStamp_abst(p.y, p.m - 1, p.d - 1, p.mi, 0)
			      + TimeStamp_sec(self) - gmoff);
}

static PyObject *
TimeStamp_raw(TimeStamp *self)
{
    return PyString_FromStringAndSize(self->data, 8);
}

static PyObject *
TimeStamp_laterThan(TimeStamp *self, PyObject *obj)
{
    TimeStamp *o = NULL;
    TimeStampParts p;
    unsigned char new[8];
    int i;

    if (obj->ob_type != self->ob_type) {
	PyErr_SetString(PyExc_TypeError, "expected TimeStamp object");
	return NULL;
    }
    o = (TimeStamp *)obj;
    if (memcmp(self->data, o->data, 8) > 0) {
	Py_INCREF(self);
	return (PyObject *)self;
    }

    memcpy(new, o->data, 8);
    for (i = 7; i > 3; i--) {
	if (new[i] == 255)
	    new[i] = 0;
	else {
	    new[i]++;
	    return TimeStamp_FromString(new);
	}
    }

    /* All but the first two bytes are the same.  Need to increment
       the year, month, and day explicitly. */
    TimeStamp_unpack(o, &p);
    if (p.mi >= 1439) {
	p.mi = 0;
	if (p.d == month_len[leap(p.y)][p.m - 1]) {
	    p.d = 1;
	    if (p.m == 12) {
		p.m = 1;
		p.y++;
	    } else
		p.m++;
	} else
	    p.d++;
    } else
	p.mi++;

    return TimeStamp_FromDate(p.y, p.m, p.d, p.mi / 60, p.mi % 60, 0);
}

static struct PyMethodDef TimeStamp_methods[] = {
    {"year", 	(PyCFunction)TimeStamp_year, 	METH_NOARGS},
    {"minute", 	(PyCFunction)TimeStamp_minute, 	METH_NOARGS},
    {"month", 	(PyCFunction)TimeStamp_month, 	METH_NOARGS},
    {"day", 	(PyCFunction)TimeStamp_day,	METH_NOARGS},
    {"hour", 	(PyCFunction)TimeStamp_hour, 	METH_NOARGS},
    {"second", 	(PyCFunction)TimeStamp_second, 	METH_NOARGS},
    {"timeTime",(PyCFunction)TimeStamp_timeTime, 	METH_NOARGS},
    {"laterThan", (PyCFunction)TimeStamp_laterThan, 	METH_O},
    {"raw",	(PyCFunction)TimeStamp_raw,	METH_NOARGS},
    {NULL,	NULL},
};

static PyTypeObject TimeStamp_type = {
    PyObject_HEAD_INIT(NULL)
    0,
    "TimeStamp",
    sizeof(TimeStamp),
    0,
    (destructor)TimeStamp_dealloc,	/* tp_dealloc */
    0,					/* tp_print */
    0,					/* tp_getattr */
    0,					/* tp_setattr */
    (cmpfunc)TimeStamp_compare,		/* tp_compare */
    0,					/* tp_repr */
    0,					/* tp_as_number */
    0,					/* tp_as_sequence */
    0,					/* tp_as_mapping */
    (hashfunc)TimeStamp_hash,		/* tp_hash */
    0,					/* tp_call */
    0,					/* tp_str */
    0,					/* tp_getattro */
    0,					/* tp_setattro */
    0,					/* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
    0,					/* tp_doc */
    0,					/* tp_traverse */
    0,					/* tp_clear */
    0,					/* tp_richcompare */
    0,					/* tp_weaklistoffset */
    0,					/* tp_iter */
    0,					/* tp_iternext */
    TimeStamp_methods,			/* tp_methods */
    0,					/* tp_members */
    0,					/* tp_getset */
    0,					/* tp_base */
    0,					/* tp_dict */
    0,					/* tp_descr_get */
    0,					/* tp_descr_set */
};

PyObject *
TimeStamp_FromString(const char *buf)
{
    /* buf must be exactly 8 characters */
    TimeStamp *ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type);
    memcpy(ts->data, buf, 8);
    return (PyObject *)ts;
}

#define CHECK_RANGE(VAR, LO, HI) if ((VAR) < (LO) || (VAR) > (HI)) { \
     return PyErr_Format(PyExc_ValueError, \
			 # VAR " must be between %d and %d: %d", \
			 (LO), (HI), (VAR)); \
    }

PyObject *
TimeStamp_FromDate(int year, int month, int day, int hour, int min, 
		   double sec)
{
    TimeStamp *ts = NULL;
    int d;
    unsigned int v;

    if (year < 1900) 
	return PyErr_Format(PyExc_ValueError,
			    "year must be greater than 1900: %d", year);
    CHECK_RANGE(month, 1, 12);
    d = days_in_month(year, month - 1);
    if (day < 1 || day > d)
	return PyErr_Format(PyExc_ValueError,
			    "day must be between 1 and %d: %d", d, day);
    CHECK_RANGE(hour, 0, 23);
    CHECK_RANGE(min, 0, 59);
    /* Seconds are allowed to be anything, so chill
       If we did want to be pickly, 60 would be a better choice.
    if (sec < 0 || sec > 59)
	return PyErr_Format(PyExc_ValueError,
			    "second must be between 0 and 59: %f", sec);
    */
    ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type);
    v = (((year - 1900) * 12 + month - 1) * 31 + day - 1);
    v = (v * 24 + hour) * 60 + min;
    ts->data[0] = v / 16777216;
    ts->data[1] = (v % 16777216) / 65536;
    ts->data[2] = (v % 65536) / 256;
    ts->data[3] = v % 256;
    sec /= SCONV;
    v = (unsigned int)sec;
    ts->data[4] = v / 16777216;
    ts->data[5] = (v % 16777216) / 65536;
    ts->data[6] = (v % 65536) / 256;
    ts->data[7] = v % 256;

    return (PyObject *)ts;
}

PyObject *
TimeStamp_TimeStamp(PyObject *obj, PyObject *args)
{
    char *buf = NULL;
    int len = 0, y, mo, d, h = 0, m = 0;
    double sec = 0;

    if (PyArg_ParseTuple(args, "s#:TimeStamp", &buf, &len)) {
	if (len != 8) {
	    PyErr_SetString(PyExc_ValueError, "8-character string expected");
	    return NULL;
	}
	return TimeStamp_FromString(buf);
    }
    PyErr_Clear();

    if (!PyArg_ParseTuple(args, "iii|iid", &y, &mo, &d, &h, &m, &sec))
	return NULL;
    return TimeStamp_FromDate(y, mo, d, h, m, sec);
}

static PyMethodDef TimeStampModule_functions[] = {
    {"TimeStamp",	TimeStamp_TimeStamp,	METH_VARARGS},
    {NULL,		NULL},
};


void
init_TimeStamp(void)
{
    PyObject *m;

    if (TimeStamp_init_gmoff() < 0)
	return;

    m = Py_InitModule4("_TimeStamp", TimeStampModule_functions,
		       TimeStampModule_doc, NULL, PYTHON_API_VERSION);
    if (m == NULL)
	return;

    TimeStamp_type.ob_type = &PyType_Type;
    TimeStamp_type.tp_getattro = PyObject_GenericGetAttr;
}
 


=== Added File Zope3/src/zodb/config.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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
#
##############################################################################
"""Default storage types.

Adapted from DBTab/StorageTypes.py.
"""

import re

from ZConfig.Config import asBoolean


def convertFileStorageArgs(quota=None, stop=None, **kw):
    if kw.has_key('name'):
        # FileStorage doesn't accept a 'name' arg
        del kw['name']
    if quota is not None:
        kw['quota'] = long(quota) or None
    if stop is not None:
        stop = long(stop)
        if not stop:
            stop = None
        else:
            from zodb.utils import p64
            stop = p64(stop)
        kw['stop'] = stop

    # Boolean args
    for name in (
        'create', 'read_only'
        ):
        if kw.has_key(name):
            kw[name] = asBoolean(kw[name])

    return kw


# Match URLs of the form 'zeo://zope.example.com:1234'
zeo_url_re = re.compile('zeo:/*(?P<host>[A-Za-z0-9\.-]+):(?P<port>[0-9]+)')

def convertAddresses(s):
    # Allow multiple addresses using semicolons as a split character.
    res = []
    for a in s.split(';'):
        a = a.strip()
        if a:
            mo = zeo_url_re.match(a)
            if mo is not None:
                # ZEO URL
                host, port = mo.groups()
                res.append((host, int(port)))
            else:
                # Socket file
                res.append(a)
    return res


def convertClientStorageArgs(addr=None, **kw):
    if addr is None:
        raise RuntimeError, 'An addr parameter is required for ClientStorage.'
    kw['addr'] = convertAddresses(addr)

    # Integer args
    for name in (
        'cache_size', 'min_disconnect_poll', 'max_disconnect_poll',
        ):
        if kw.has_key(name):
            kw[name] = int(kw[name])

    # Boolean args
    for name in (
        'wait', 'read_only', 'read_only_fallback',
        ):
        if kw.has_key(name):
            kw[name] = asBoolean(kw[name])

    # The 'client' parameter must be None to be false.  Yuck.
    if kw.has_key('client') and not kw['client']:
        kw['client'] = None

    return kw


# Currently unused
def convertBDBStorageArgs(**kw):
    from zodb.storage.base import BerkeleyConfig
    config = BerkeleyConfig()
    for name in dir(BerkeleyConfig):
        if name.startswith('_'):
            continue
        val = kw.get(name)
        if val is not None:
            if name == 'read_only':
                val = asBoolean(val)
            elif name != 'logdir':
                val = int(val)
            setattr(config, name, val)
            del kw[name]
    # XXX: Nobody ever passes in env
    assert not kw.has_key('env')
    kw['config'] = config
    return kw


storage_types = {
    'FileStorage': ('ZODB.FileStorage', convertFileStorageArgs),
    'MappingStorage': ('ZODB.MappingStorage', None),
    'ClientStorage': ('ZEO.ClientStorage', convertClientStorageArgs),
    'BDBFullStorage': ('BDBStorage.BDBFullStorage', convertBDBStorageArgs),
    'BDBMinimalStorage': ('BDBStorage.BDBMinimalStorage',
                          convertBDBStorageArgs),
    }


"""Higher-level support for configuring storages.

Storages are configured a la DBTab.

A storage section has the form

  <Storage Name (dependent)>
    # For example
    type        FileStorage
    file_name   var/Data.fs
    read_only   1
  </Storage>

where Name and (dependent) are optional.  Once you have retrieved the
section object (probably with getSection("Storage", name), the
function creatStorage() in this module will create the storage object
for you.
"""



def createStorage(section):
    """Create a storage specified by a configuration section."""
    klass, args = getStorageInfo(section)
    return klass(**args)

def getStorageInfo(section):
    """Extract a storage description from a configuration section.

    Return a tuple (klass, args) where klass is the storage class and
    args is a dictionary of keyword arguments.  To create the storage,
    call klass(**args).

    Adapted from DatabaseFactory.setStorageParams() in DBTab.py.
    """
    type = section.get("type")
    if not type:
        raise RuntimeError, "A storage type is required"
    module = None
    pos = type.rfind(".")
    if pos >= 0:
        # Specified the module
        module, type = type[:pos], type[pos+1:]
    converter = None
    if not module:
        # Use a default module and argument converter.
        info = storage_types.get(type)
        if not info:
            raise RuntimeError, "Unknown storage type: %s" % type
        module, converter = info
    m = __import__(module, {}, {}, [type])
    klass = getattr(m, type)

    args = {}
    if section.name:
        args["name"] = section.name
    for key in section.keys():
        if key.lower() != "type":
            args[key] = section.get(key)
    if converter is not None:
        args = converter(**args)
    return (klass, args)


=== Added File Zope3/src/zodb/conflict.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
# 
##############################################################################

# It's hard to draw a clear separation between these two modules,
# because conflict resolution depends (for efficiency and safety) on
# working with the raw object state instead of instantiated objects.

__metaclass__ = type

from cStringIO import StringIO
from cPickle import PicklingError
import logging

from transaction.interfaces import ConflictError
from zodb.serialize import BaseObjectReader, ObjectWriter, getClassMetadata

ResolvedSerial = "rs"

class ResolvedObjectAdapter:
    """Adapt an object's raw state to the ObjectWriter protocol.

    ObjectWriter uses an object's __class__ and __getstate__() method
    to determine how to pickle it.  When conflict resolution occurs,
    there is no instantiated object; the code deals with the concrete
    state as returned by __getstate__().  This adapter allows the
    state to be passed to ObjectWriter without instantiating the
    object.

    This object should only be used in conjunction with the ObjectWriter.
    """

    def __init__(self, ghost, state):
        self._class = ghost.__class__
        self._state = state

    def __getattribute__(self, name):
        if name == "__class__":
            return self._class
        else:
            _super = super(ResolvedObjectAdapter, self).__getattribute__
            return _super(name)

    def __getstate__(self):
        return self._state

class PersistentReference:

    __slots__ = "oid",

    def __init__(self, oid):
        self.oid = oid

class ResolveObjectReader(BaseObjectReader):

    # The bad_classes attribute tracks all classes for which an
    # _p_resolveConflict() method could not be found.  It is used
    # to avoid repeating work to load classes when it is known
    # that they can't be imported or don't resolve conflicts.
    bad_classes = {}

    def __init__(self):
        self._refs = {}

    def _persistent_load(self, oid):
        ref = self._refs.get(oid)
        if ref is None:
            ref = self._refs[oid] = PersistentReference(oid)
        return ref

    def unresolvable(cls, klass):
        """Returns True if class does not support conflict resolution.

        The exact rules are implementation dependent.  This method was
        written to make testing easier.
        """
        meta = getClassMetadata(klass=klass)
        return meta in cls.bad_classes

    unresolvable = classmethod(unresolvable)

    def getClassMetadata(self, pickle):
        unpickler = self._get_unpickler(pickle)
        classmeta = unpickler.load()
        return classmeta

    def getResolver(self, pickle):
        # Get the conflict resolution method from a ghost rather
        # than actually instantiating the object.  _p_resolveConflict()
        # is really a static method.
        meta = self.getClassMetadata(pickle)
        if meta in self.bad_classes:
            return None
        try:
            ghost = self._new_object(*meta)
        except ImportError:
            # log failure to import?
            self.bad_classes[meta] = True
            return None
        if ghost is None:
            return None
        resolve = getattr(ghost, "_p_resolveConflict", None)
        if resolve is None:
            self.bad_classes[meta] = True
            return None
        else:
            return resolve

class ConflictResolvingStorage:
    "Mix-in class that provides conflict resolution handling for storages"

    def tryToResolveConflict(self, oid, committedSerial, oldSerial, newpickle,
                             committedData=None):
        reader = ResolveObjectReader()
        resolve = reader.getResolver(newpickle)
        if resolve is None:
            return None
        newstate = reader.getState(newpickle)

        p = self.loadSerial(oid, oldSerial)
        try:
            old = reader.getState(p)
        except (EOFError, PicklingError), err:
            logging.warn("CR: Error loading object: %s", err)
            return None
        if committedData is None:
            committedData = self.loadSerial(oid, committedSerial)
        try:
            committed = reader.getState(committedData)
        except (EOFError, PicklingError), err:
            logging.warn("CR: Error loading object: %s", err)
            return None
        try:
            resolved = resolve(old, committed, newstate)
        except ConflictError:
            return None

        writer = ObjectWriter()
        obj = ResolvedObjectAdapter(resolve.im_self, resolved)
        return writer.getState(obj)


=== Added File Zope3/src/zodb/connection.py === (564/664 lines abridged)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
# 
##############################################################################
from zodb import POSException
from zodb.utils import p64, u64, Set, z64

import tempfile

class TmpStore:
    """A storage to support savepoints."""

    _bver = ''

    def __init__(self, base_version):
        self._transaction = None
        if base_version:
            self._bver = base_version
        self._file = tempfile.TemporaryFile()
        # _pos: current file position
        # _tpos: file position at last commit point
        self._pos = self._tpos = 0
        # _index: map oid to pos of last committed version
        self._index = {}
        # _tindex: map oid to pos for new updates
        self._tindex = {}
        self._created = Set()
        self._db = None

    def close(self):
        # XXX Is this necessary?
        self._file.close()

    def getName(self):
        return self._db.getName()
    
    def getSize(self):
        return self._pos

    def load(self, oid, version):
        pos = self._index.get(oid, None)

[-=- -=- -=- 564 lines omitted -=- -=- -=-]

        # (self._invalidate_modified) while it still has its
        # lock.  We don't want another thread to be able to read any
        # updated data until we've had a chance to send an
        # invalidation message to all of the other connections!

        self._db.begin_invalidation()
        # XXX We should really have a try/finally because the begin
        # call acquired a lock that will only be released in
        # _invalidate_modified().
        self._storage.tpc_finish(txn, self._invalidate_modified)
        try:
            del self._txns[txn]
        except KeyError:
            pass

        self._flush_invalidations()

    def _invalidate_modified(self):
        for oid in self._modified:
            self._db.invalidate(oid, self)
        self._db.finish_invalidation()

    def sync(self):
        # XXX Is it safe to abort things right now?
        get_transaction().abort()
        sync = getattr(self._storage, 'sync', None)
        if sync is not None:
            sync()
        self._flush_invalidations()
        
class Rollback:
    """Rollback changes associated with savepoint"""

    # In order to rollback changes for a savepoint(), we must remove
    # the logged changes from the TmpStore and invalidate any object
    # that has been changed since the rolledback transaction started.

    # XXX Should it be possible to rollback() to the same savepoint
    # more than once?

    def __init__(self, conn, tmp_undo):
        self._conn = conn
        self._tmp_undo = tmp_undo # undo info from the storage

    def rollback(self):
        if not self._tmp_undo.current(self._conn._storage):
            msg = "savepoint has already been committed"
            raise RollbackError(msg)
        self._tmp_undo.rollback()
        self._conn._cache.invalidateMany(self._conn._modified)


=== Added File Zope3/src/zodb/db.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
# 
##############################################################################
"""Database objects

$Id: db.py,v 1.1.2.1 2002/12/23 19:30:43 jim Exp $
"""

__metaclass__ = type

import cPickle, cStringIO, sys
from threading import Lock
from time import time, ctime
from types import StringType
import logging

from zodb import POSException
from zodb.connection import Connection
from zodb.serialize import getDBRoot
from zodb.ztransaction import Transaction
from zodb.utils import z64

from transaction import get_transaction
from transaction.interfaces import IDataManager

class DB:
    """The Object Database

    The Object database coordinates access to and interaction of one
    or more connections, which manage object spaces.  Most of the actual work
    of managing objects is done by the connections.
    """
    def __init__(self, storage,
                 pool_size=7,
                 cache_size=400,
                 ):
        """Create an object database.

        The storage for the object database must be passed in.
        Optional arguments are:

        pool_size -- The size of the pool of object spaces.

        """

        # Allocate locks:
        l=Lock()
        self._a=l.acquire
        self._r=l.release

        # Setup connection pools and cache info
        self._pool = []
        self._allocated = []
        self._pool_lock = Lock()
        self._pool_lock.acquire()
        self._temps = []
        self._pool_size = pool_size
        self._cache_size = cache_size

        # Setup storage
        self._storage = storage
        storage.registerDB(self)
        try:
            storage.load(z64, "")
        except KeyError:
            # Create the database's root in the storage if it doesn't exist
            t = Transaction(description="initial database creation")
            storage.tpc_begin(t)
            storage.store(z64, None, getDBRoot(), '', t)
            storage.tpc_vote(t)
            storage.tpc_finish(t)

        # Pass through methods:
        for m in ('history', 'supportsVersions', 'undoInfo', 'versionEmpty',
                  'versions', 'modifiedInVersion', 'versionEmpty'):
            setattr(self, m, getattr(storage, m))

    def _closeConnection(self, connection):
        """Return a connection to the pool"""
        self._a()
        try:
            version = connection._version
            self._allocated.remove(connection)
            self._pool.append(connection)
            if len(self._pool) == 1:
                # Pool now usable again, unlock it.
                self._pool_lock.release()
        finally: self._r()
        
    def _connectionMap(self, f):
        self._a()
        try:
            map(f, self._allocated)

            # XXX I don't understand what this code is trying to do
            if self._temps:
                for cc in self._temps:
                    if sys.getrefcount(cc) > 3:
                        f(cc)
                self._temps = []
        finally: self._r()

    def abortVersion(self, version):
        AbortVersion(self, version)

    # XXX I don't think the cache should be used via _cache.
    # Not sure that both full sweep and minimize need to stay.

    def cacheFullSweep(self):
        self._connectionMap(lambda c: c._cache.full_sweep())

    def cacheMinimize(self):
        self._connectionMap(lambda c: c._cache.minimize())

    def close(self):
        self._storage.close()

    def commitVersion(self, source, destination=''):
        CommitVersion(self, source, destination)

    def getCacheSize(self):
        return self._cache_size

    def getName(self):
        return self._storage.getName()

    def getPoolSize(self):
        return self._pool_size

    def begin_invalidation(self):
        # Must be called before first call to invalidate and before
        # the storage lock is held.
        self._a()

    def finish_invalidation(self):
        # Must be called after begin_invalidation() and after final
        # invalidate() call.
        self._r()

    def invalidate(self, oid, connection=None, version=''):
        """Invalidate references to a given oid.

        This is used to indicate that one of the connections has committed a
        change to the object.  The connection commiting the change should be
        passed in to prevent useless (but harmless) messages to the
        connection.
        """
        assert oid is not None
        if connection is not None:
            assert version == connection._version
            version = connection._version

        # Notify connections
        for cc in self._allocated:
            if cc is not connection:
                self.invalidateConnection(cc, oid, version)

        if self._temps:
            # t accumulates all the connections that aren't closed.
            t = []
            for cc in self._temps:
                if cc is not connection:
                    self.invalidateConnection(cc, oid, version,
                                              t.append)
            self._temps = t

    def invalidateConnection(self, conn, oid, version, alive=None):
        """Send invalidation message to conn for oid on version.

        If the modification occurred on a version, an invalidation is
        sent only if the version of the mod matches the version of the
        connection.

        This function also handles garbage collection of connection's
        that aren't used anymore.  If the optional argument alive is
        defined, it is a function that is called for all connections
        that aren't garbage collected.
        """

        # XXX use weakrefs instead of refcounts?
        if sys.getrefcount(conn) <= 3:
            conn.close()
        else:
            if alive is not None:
                alive(conn)
        if not version or conn.getVersion() == version:
            conn.invalidate(oid)

    def open(self, version='', transaction=None, temporary=0, force=None,
             waitflag=1):
        """Return a object space (AKA connection) to work in

        The optional version argument can be used to specify that a
        version connection is desired.

        The optional transaction argument can be provided to cause the
        connection to be automatically closed when a transaction is
        terminated.  In addition, connections per transaction are
        reused, if possible.

        Note that the connection pool is managed as a stack, to increate the
        likelihood that the connection's stack will include useful objects.
        """
        self._a()
        try:

            if transaction is not None:
                connections=transaction._connections
                if connections:
                    v = connections.get(version)
                    if not (v is None or temporary):
                        return v
                else:
                    transaction._connections = connections = {}
                transaction = transaction._connections

            if temporary:
                # This is a temporary connection.
                # We won't bother with the pools.  This will be
                # a one-use connection.
                c = Connection(self, version, cache_size=self._cache_size)
                self._temps.append(c)
                if transaction is not None:
                    transaction[id(c)] = c
                return c

            # Pool locks are tricky.  Basically, the lock needs to be
            # set whenever the pool becomes empty so that threads are
            # forced to wait until the pool gets a connection in it.
            # The lock is acquired when the (empty) pool is
            # created. The The lock is acquired just prior to removing
            # the last connection from the pool and just after adding
            # a connection to an empty pool.

            if not self._pool:
                c = None
                if self._pool_size > len(self._pool) or force:
                    c = Connection(self, version, cache_size=self._cache_size)
                    self._pool.append(c)
                    
                if c is None:
                    if waitflag:
                        self._r()
                        self._pool_lock.acquire()
                        self._a()
                        if len(self._pool) > 1:
                            # Note that the pool size will normally be 1 here,
                            # but it could be higher due to a race condition.
                            self._pool_lock.release()
                    else:
                        return
            elif len(self._pool) == 1:
                # Taking last one, lock the pool
                # Note that another thread might grab the lock
                # before us, so we might actually block, however,
                # when we get the lock back, there *will* be a
                # connection in the pool.
                self._r()
                self._pool_lock.acquire()
                self._a()
                if len(self._pool) > 1:
                    # Note that the pool size will normally be 1 here,
                    # but it could be higher due to a race condition.
                    self._pool_lock.release()

            # XXX Could look for a connection with the right version
            c = self._pool.pop()
            c.reset(version)
            self._allocated.append(c)
            for other_conn in self._pool:
                other_conn.cacheGC()

            if transaction is not None:
                transaction[version] = c
            return c

        finally: self._r()

    def pack(self, t=None, days=0):
        if t is None:
            t = time()
        t -= days * 86400
        try:
            self._storage.pack(t)
        except:
            logging.getLogger("ZODB").exception("packing")
            raise
                           
    def setCacheSize(self, v):
        self._cache_size = v
        for c in self._pool:
            c._cache.cache_size = v

    def setPoolSize(self, v):
        self._pool_size = v
    
    def undo(self, id):
        TransactionalUndo(self, id)

class SimpleDataManager:

    __implements__ = IDataManager
    
    def __init__(self, db):
        self._db = db
        self._storage = db._storage
        get_transaction().join(self)

    def prepare(self, txn):
        self._storage.tpc_begin(txn)
        try:
            self._prepare(txn)
            self._storage.tpc_vote(txn)
        except POSException.StorageError, err:
            logging.getLogger("DB").info("Error during prepare: %s", err)
            return False
        else:
            return True

    def abort(self, txn):
        self._storage.tpc_abort(txn)

    def commit(self, txn):
        self._storage.tpc_finish(txn)

    def _prepare(self, txn):
        # Hook for clients to perform action during 2PC
        pass

class CommitVersion(SimpleDataManager):
    """An object that will see to version commit."""

    def __init__(self, db, version, dest=''):
        super(CommitVersion, self).__init__(db)
        self._version = version
        self._dest = dest

    def _prepare(self, txn):
        self._oids = self._storage.commitVersion(self._version, self._dest,
                                                 txn)

    def commit(self, txn):
        super(CommitVersion, self).commit(txn)
        for oid in self._oids:
            self._db.invalidate(oid, version=self._dest)
        if self._dest:
            # the code above just invalidated the dest version.
            # now we need to invalidate the source!
            for oid in self._oids:
                self._db.invalidate(oid, version=self._version)
                
class AbortVersion(SimpleDataManager):
    """An object that will see to version abortion."""

    def __init__(self, db, version):
        super(AbortVersion, self).__init__(db)
        self._version = version

    def _prepare(self, txn):
        self._oids = self._storage.abortVersion(self._version, txn)
        
    def commit(self, txn):
        super(AbortVersion, self).commit(txn)
        for oid in self._oids:
            self._db.invalidate(oid, version=self._version)

class TransactionalUndo(SimpleDataManager):
    """An object that will see to transactional undo."""
    
    def __init__(self, db, tid):
        super(TransactionalUndo, self).__init__(db)
        self._tid = tid

    def _prepare(self, txn):
        self._oids = self._storage.transactionalUndo(self._tid, txn)
        
    def commit(self, txn):
        super(TransactionalUndo, self).commit(txn)
        for oid in self._oids:
            self._db.invalidate(oid)


=== Added File Zope3/src/zodb/dbdump.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
# 
##############################################################################
"""Provide a text dump of a storage based on a storage iterator."""

from zodb.storage.file import FileIterator
from zodb.timestamp import TimeStamp
from zodb.utils import u64
from zodb.storage.tests.base import zodb_unpickle
from zodb.serialize import SimpleObjectReader

import md5
import time

def dbdump(iter, outp=None, with_offset=True):
    i = 0
    for trans in iter:
        t = TimeStamp(trans.tid).timeTime()
        # special case just for FileStorage
        if with_offset and hasattr(trans, "_pos"):
            print >> outp, "Trans #%05d tid=%016x time=%s offset=%d" % \
                  (i, u64(trans.tid), time.ctime(t), trans._pos)
        else:
            print >> outp, "Trans #%05d tid=%016x time=%s" % \
                  (i, u64(trans.tid), time.ctime(t))
        print >> outp, "\tstatus=%s user=%s description=%s" % \
              (`trans.status`, trans.user, trans.description)
        j = 0
        for rec in trans:
            if rec.data is None:
                fullclass = "undo or abort of object creation"
            else:
                # Any object reader will do
                reader = SimpleObjectReader()
                fullclass = reader.getClassName(rec.data)
                dig = md5.new(rec.data).hexdigest()
            # special case for testing purposes
            if fullclass == "ZODB.tests.MinPO.MinPO":
                obj = zodb_unpickle(rec.data)
                fullclass = "%s %s" % (fullclass, obj.value)
            if rec.version:
                version = "version=%s " % rec.version
            else:
                version = ''
            print >> outp, "  data #%05d oid=%016x %sclass=%s" % \
                  (j, u64(rec.oid), version, fullclass)
            j += 1
        print >> outp
        i += 1
    iter.close()

def fsdump(path, outp=None, with_offset=True):
    iter = FileIterator(path)
    dbdump(iter, outp, with_offset)


=== Added File Zope3/src/zodb/export.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
# 
##############################################################################
"""Support for database export and import."""

from zodb.interfaces import ExportError
from zodb.utils import p64, u64, Set
from zodb.serialize import findrefs, ObjectCopier
from transaction import get_transaction

from cStringIO import StringIO
from cPickle import Pickler, Unpickler
from tempfile import TemporaryFile
from types import StringType, TupleType

export_end_marker = '\377' * 16

class ExportImport:
    # a mixin for use with ZODB.Connection.Connection

    __hooks = None

    def exportFile(self, oid, file=None):
        if file is None:
            file = TemporaryFile()
        elif isinstance(file, StringType):
            file = open(file, 'w+b')
        file.write('ZEXP')
        oids = [oid]
        done_oids = Set()
        while oids:
            oid = oids.pop(0)
            if oid in done_oids:
                continue
            done_oids.add(oid)
            try:
                p, serial = self._storage.load(oid, self._version)
            except:
                # XXX what exception is expected?
                pass # Ick, a broken reference
            else:
                oids += findrefs(p)
                file.write(oid)
                file.write(p64(len(p)))
                file.write(p)
        file.write(export_end_marker)
        return file

    def importFile(self, file, clue=None, customImporters=None):
        # This is tricky, because we need to work in a transaction!
        # XXX I think this needs to work in a transaction, because it
        # needs to write pickles into the storage, which only allows
        # store() calls between tpc_begin() and tpc_vote().

        if isinstance(file, StringType):
            file = open(file,'rb')
        magic = file.read(4)

        if magic != 'ZEXP':
            if customImporters is not None and customImporters.has_key(magic):
                file.seek(0)
                return customImporters[magic](self, file, clue)
            raise ExportError("Invalid export header")

        t = get_transaction()
        if clue is not None:
            t.note(clue)

        L = []
        if self.__hooks is None:
            self.__hooks = []
        self.__hooks.append((file, L))
        t.join(self)
        t.savepoint()
        # Return the root imported object.
        if L:
            return self[L[0]]
        else:
            return None

    def importHook(self, txn):
        if self.__hooks is None:
            return
        for file, L in self.__hooks:
            self._importDuringCommit(txn, file, L)
        del self.__hooks

    def _importDuringCommit(self, txn, file, return_oid_list):
        """Invoked by the transaction manager mid commit.
        
        Appends one item, the OID of the first object created,
        to return_oid_list.
        """
        copier = ObjectCopier(self, self._storage, self._created)

        while 1:
            h = file.read(16)
            if h == export_end_marker:
                break
            if len(h) != 16:
                raise ExportError("Truncated export file")
            l = u64(h[8:16])
            p = file.read(l)
            if len(p) != l:
                raise ExportError("Truncated export file")

            # XXX I think it would be better if copier.copy()
            # returned an oid and a new pickle so that this logic
            # wasn't smeared across to modules.
            oid = h[:8]
            new_ref = copier.oids.get(oid)
            if new_ref is None:
                new_oid = self._storage.new_oid()
                copier.oids[oid] = new_oid, None
                return_oid_list.append(new_oid)
                self._created.add(new_oid)
            else:
                new_oid = new_ref[0]

            new = copier.copy(p)
            self._storage.store(new_oid, None, new, self._version, txn)


=== Added File Zope3/src/zodb/interfaces.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
# 
##############################################################################
"""ZODB-defined exceptions

$Id: interfaces.py,v 1.1.2.1 2002/12/23 19:30:43 jim Exp $
"""

from transaction.interfaces \
     import TransactionError, RollbackError, ConflictError as _ConflictError

from types import StringType, DictType
import zodb.utils

def _fmt_oid(oid):
    return "%016x" % ZODB.utils.u64(oid)

def _fmt_undo(oid, reason):
    s = reason and (": %s" % reason) or ""
    return "Undo error %s%s" % (_fmt_oid(oid), s)

class POSError(StandardError):
    """Persistent object system error."""

class POSKeyError(KeyError, POSError):
    """Key not found in database."""

    def __str__(self):
        return _fmt_oid(self.args[0])

class ConflictError(_ConflictError):
    """Two transactions tried to modify the same object at once.

    This transaction should be resubmitted.

    Instance attributes:
      oid : string
        the OID (8-byte packed string) of the object in conflict
      class_name : string
        the fully-qualified name of that object's class
      message : string
        a human-readable explanation of the error
      serials : (string, string)
        a pair of 8-byte packed strings; these are the serial numbers
        (old and new) of the object in conflict.  (Serial numbers are
        closely related [equal?] to transaction IDs; a ConflictError may
        be triggered by a serial number mismatch.)

    The caller should pass either object or oid as a keyword argument,
    but not both of them.  If object is passed, it should be a
    persistent object with an _p_oid attribute.
    """

    def __init__(self, message=None, object=None, oid=None, serials=None):
        if message is None:
            self.message = "database conflict error"
        else:
            self.message = message

        if object is None:
            self.oid = None
            self.class_name = None
        else:
            self.oid = object._p_oid
            klass = object.__class__
            self.class_name = klass.__module__ + "." + klass.__name__

        if oid is not None:
            assert self.oid is None
            self.oid = oid

        self.serials = serials

    def __str__(self):
        extras = []
        if self.oid:
            extras.append("oid %s" % _fmt_oid(self.oid))
        if self.class_name:
            extras.append("class %s" % self.class_name)
        if self.serials:
            extras.append("serial was %s, now %s" %
                          tuple(map(_fmt_oid, self.serials)))
        if extras:
            return "%s (%s)" % (self.message, ", ".join(extras))
        else:
            return self.message

    def get_oid(self):
        return self.oid

    def get_class_name(self):
        return self.class_name

    def get_old_serial(self):
        return self.serials[0]

    def get_new_serial(self):
        return self.serials[1]

    def get_serials(self):
        return self.serials

class ReadConflictError(ConflictError):
    """Conflict detected when object was loaded.

    An attempt was made to read an object that has changed in another
    transaction (eg. another thread or process).
    """
    def __init__(self, message=None, object=None, serials=None):
        if message is None:
            message = "database read conflict error"
        ConflictError.__init__(self, message=message, object=object,
                               serials=serials)

class DanglingReferenceError(TransactionError):
    """An object has a persistent reference to a missing object.

    If an object is stored and it has a reference to another object
    that does not exist (for example, it was deleted by pack), this
    exception may be raised.  Whether a storage supports this feature,
    it a quality of implementation issue.

    Instance attributes:
    referer: oid of the object being written
    missing: referenced oid that does not have a corresponding object
    """

    def __init__(self, Aoid, Boid):
        self.referer = Aoid
        self.missing = Boid

    def __str__(self):
        return "from %s to %s" % (_fmt_oid(self.referer),
                                  _fmt_oid(self.missing))

class VersionError(POSError):
    """An error in handling versions occurred."""

class VersionCommitError(VersionError):
    """An invalid combination of versions was used in a version commit."""

class VersionLockError(VersionError, TransactionError):
    """Can't modify an object that is modified in unsaved version."""

    def __init__(self, oid, version):
        self.oid = oid
        self.version = version

    def __str__(self):
        return "%s locked in version %r" % (_fmt_oid(self.oid),
                                            self.version)

class UndoError(POSError):
    """An attempt was made to undo a non-undoable transaction."""

    def __init__(self, oid, reason=None):
        self._oid = oid
        self._reason = reason

    def __str__(self):
        return _fmt_undo(self._oid, self._reason)

class MultipleUndoErrors(UndoError):
    """Several undo errors occured during a single transaction."""
    
    def __init__(self, errs):
        # provide an oid and reason for clients that only look at that
        UndoError.__init__(self, *errs[0])
        self._errs = errs

    def __str__(self):
        return "\n".join([_fmt_undo(*pair) for pair in self._errs])

class StorageError(POSError):
    """Base class for storage based exceptions."""

class StorageTransactionError(StorageError):
    """An operation was invoked for an invalid transaction or state."""

class StorageSystemError(StorageError):
    """Panic! Internal storage error!"""

class MountedStorageError(StorageError):
    """Unable to access mounted storage."""

class ReadOnlyError(StorageError):
    """Unable to modify objects in a read-only storage."""

class TransactionTooLargeError(StorageTransactionError):
    """The transaction exhausted some finite storage resource."""

class ExportError(POSError):
    """An export file doesn't have the right format."""

class Unsupported(POSError):
    """An feature that is unsupported bt the storage was used."""
    
class InvalidObjectReference(POSError):
    """An object contains an invalid reference to another object.

    An invalid reference may be one of:

    o A reference to a wrapped persistent object.

    o A reference to an object in a different database connection.
    """


from zope.interface import Interface
from zope.interface.element import Attribute

from transaction.interfaces import ITransaction as _ITransaction

class IConnection(Interface):
    """Interface required of Connection by ZODB DB.

    The Connection also implements IPersistentDataManager.
    """

    def reset(version):
        """Reset connection to use specified version."""

    def getVersion():
        """Return the version that connection is using."""

    def invalidate(oid):
        """Invalidate a particular oid

        This marks the oid as invalid, but doesn't actually invalidate
        it.  The object data will be actually invalidated at certain
        transaction boundaries.

        XXX The code suggests that invalidate() may sometimes be
        called with None as the oid, which would mean the "entire
        transaction" is invalidated.
        """
        
    def close():
        pass

    def cacheGC():
        pass

class ITransaction(_ITransaction):
    """Extends base ITransaction with with metadata.

    Client code should use this interface to set attributes.
    """

    def note(text):
        """Add the text to the transaction description

        If there previous description isn't empty, a blank line is
        added before the new text.
        """

    def setUser(user_name, path="/"):
        """Set the transaction user name.

        The user is actually set to the path followed by a space and
        the user name.
        """

    def setExtendedInfo(name, value):
        """Set extended information."""

class ITransactionAttrs(_ITransaction):
    # XXX The following attributes used by storages, so they are part
    # of the interface.  But I'd rather not have user code explicitly
    # use the attributes.

    user = Attribute("The user as set by setUser()")
    
    description = Attribute("A description as set by note()")
    
    _extension = Attribute(
        """Extended info as set by setExtendedInfo()

        Should be None or a dictionary.""")
    


=== Added File Zope3/src/zodb/lockfile.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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
#
##############################################################################

from zodb.interfaces import StorageSystemError

# Try to create a function that creates Unix file locks.
try:
    import fcntl

    lock_file_FLAG = fcntl.LOCK_EX | fcntl.LOCK_NB

    def lock_file(file):
        try:
            un = file.fileno()
        except:
            return # don't care if not a real file

        try:
            fcntl.flock(un, lock_file_FLAG)
        except:
            raise StorageSystemError, (
                "Could not lock the database file.  There must be\n"
                "another process that has opened the file.\n")

except:
    # Try windows-specific code:
    try:
        from zodb.winlock import LockFile
        def lock_file(file):
            try:
                un=file.fileno()
            except:
                return # don't care if not a real file

            try:
                LockFile(un, 0, 0, 1, 0) # just lock the first byte, who cares
            except:
                raise StorageSystemError, (
                    "Could not lock the database file.  There must be\n"
                    "another process that has opened the file.\n")
    except:
        import logging
        def lock_file(file):
            logging.warn("FS: No file-locking support on this platform")


=== Added File Zope3/src/zodb/serialize.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Support for ZODB object serialization.

ZODB serializes objects using a custom format based on Python pickles.
When an object is unserialized, it can be loaded as either a ghost or
a real object.  A ghost is a persistent object of the appropriate type
but without any state.  The first time a ghost is accessed, the
persistence machinery traps access and loads the actual state.  A
ghost allows many persistent objects to be loaded while minimizing the
memory consumption of referenced but otherwise unused objects.

Object introspection
--------------------

XXX Need to define what properties an object must have to be usable
with the ObjectWriter.  Should document how it determines what the
class and state of an object are.

Pickle format
-------------

ZODB pickles objects using a custom format.  Each object pickle had
two parts: the class description and the object state.  The class
description must provide enough information to call the class's
``__new__`` and create an empty object.  Once the object exists, its
state is passed to ``__getstate__``.

The class metadata is a three-tuple contained the module name, the
class name, and a tuple of arguments to pass to ``__new__``.  The last
element may be None if the only argument to ``__new__`` is the class.

Persistent references
---------------------
A persistent reference is a pair containing an oid and class metadata.
XXX Need to write more about when they are used and when plain oids
are used.
"""

__metaclass__ = type

from cStringIO import StringIO
import cPickle
from types import StringType, TupleType
import logging

def getClass(module, name):
    mod = __import__(module)
    parts = module.split(".")
    for part in parts[1:]:
        mod = getattr(mod, part)
    return getattr(mod, name)

def getClassMetadata(obj=None, klass=None):
    if klass is None:
        klass = obj.__class__
    module = klass.__module__
    classname = klass.__name__
    # XXX what if obj is None and we were passed klass?
    if hasattr(obj, "__getnewargs__"):
        newargs = obj.__getnewargs__()
    else:
        newargs = None
    return module, classname, newargs

class RootJar:
    def new_oid(self):
        return "\0" * 8

def getDBRoot():
    """Return a serialized database root object."""
    # Need for the initial bootstrap
    writer = ObjectWriter(RootJar())
    from persistence.dict import PersistentDict
    root = PersistentDict()
    return writer.getState(root)

class ObjectWriter:

    def __init__(self, jar=None):
        self._file = StringIO()
        self._p = cPickle.Pickler(self._file, 1)
        self._p.persistent_id = self._persistent_id
        self._stack = []
        if jar is not None:
            assert hasattr(jar, "new_oid")
        self._jar = jar
        
    def _persistent_id(self, obj):
        """Test if an object is persistent, returning an oid if it is.

        This function is used by the pickler to test whether an object
        is persistent.  If it isn't, the function returns None and the
        object is included in the pickle for the current persistent
        object.

        If it is persistent, it returns the oid and sometimes a tuple
        with other stuff.
        """
        if not hasattr(obj, '_p_oid'):
            return None
        
        oid = obj._p_oid

        # I'd like to write something like this --
        # if isinstance(oid, types.MemberDescriptor):
        # -- but I can't because the type doesn't have a canonical name.
        # Instead, we'll assert that an oid must always be a string
        if not (oid is None or isinstance(oid, StringType)):
            # XXX log a warning
            return None

        if oid is None or obj._p_jar is not self._jar:
            # XXX log warning if obj._p_jar is not self
            oid = self._jar.new_oid()
            obj._p_jar = self._jar
            obj._p_oid = oid
            self._stack.append(obj)

        return oid, getClassMetadata(obj)

    def newObjects(self, obj):
        # The modified object is also a "new" object.
        # XXX Should only call newObjects() once per Pickler.
        self._stack.append(obj)
        return NewObjectIterator(self._stack)

    def getState(self, obj):
        return self._dump(getClassMetadata(obj), obj.__getstate__())

    def _dump(self, classmeta, state):
        self._file.reset()
        self._p.clear_memo()
        self._p.dump(classmeta)
        self._p.dump(state)
        return self._file.getvalue()

class NewObjectIterator:

    # The pickler is used as a forward iterator when the connection
    # is looking for new objects to pickle.

    def __init__(self, stack):
        self._stack = stack

    def __iter__(self):
        return self

    def next(self):
        if self._stack:
            elt = self._stack.pop()
            return elt
        else:
            raise StopIteration

class BaseObjectReader:

    def _persistent_load(self, oid):
        # subclasses must define _persistent_load().
        raise NotImplementedError

    def _get_unpickler(self, pickle):
        file = StringIO(pickle)
        unpickler = cPickle.Unpickler(file)
        unpickler.persistent_load = self._persistent_load
        return unpickler
        
    def _new_object(self, module, classname, newargs=None):
        klass = getClass(module, classname)
        if newargs is None:
            obj = klass.__new__(klass)
        else:
            obj = klass.__new__(klass, *newargs)
                
        return obj

    def getClassName(self, pickle):
        unpickler = self._get_unpickler(pickle)
        module, classname, newargs = unpickler.load()
        return "%s.%s" % (module, classname)

    def getGhost(self, pickle):
        unpickler = self._get_unpickler(pickle)
        module, classname, newargs = unpickler.load()
        return self._new_object(module, classname, newargs)

    def getState(self, pickle):
        unpickler = self._get_unpickler(pickle)
        unpickler.load() # skip the class metadata
        state = unpickler.load()
        return state

    def setGhostState(self, object, pickle):
        state = self.getState(pickle)
        object.__setstate__(state)

    def getObject(self, pickle):
        unpickler = self._get_unpickler(pickle)
        module, classname, newargs = unpickler.load()
        obj = self._new_object(module, classname, newargs)
        state = unpickler.load()
        obj.__setstate__(state)
        return obj

class SimpleObjectReader(BaseObjectReader):
    """Minimal reader for a single data record."""

    def _persistent_load(self, oid):
        return None

class ConnectionObjectReader(BaseObjectReader):

    def __init__(self, conn, cache):
        self._conn = conn
        self._cache = cache

    def _persistent_load(self, oid):
        # persistent_load function to pass to ObjectReader
        if isinstance(oid, TupleType):
            # XXX We get here via new_persistent_id()
            
            # Quick instance reference.  We know all we need to know
            # to create the instance w/o hitting the db, so go for it!
            oid, classmeta = oid
            obj = self._cache.get(oid)
            if obj is not None:
                return obj

            obj = self._new_object(*classmeta)

            # XXX should be done by connection
            obj._p_oid = oid
            obj._p_jar = self._conn
            obj._p_changed = None
            
            self._cache[oid] = obj
            return obj

        obj = self._cache.get(oid)
        if obj is not None:
            return obj
        return self._conn[oid]

class CopyReference:
    def __init__(self, ref):
        self.ref = ref

class CopyObjectReader(BaseObjectReader):

    def __init__(self, storage, created, oids):
        self._storage = storage
        self._created = created
        self._cache = oids

    def _persistent_load(self, oid):
        if isinstance(oid, TupleType):
            oid, classmeta = oid
        else:
            classmeta = None
        new_ref = self._cache.get(oid)
        if new_ref is None:
            new_oid = self._storage.new_oid()
            self._created.add(new_oid)
            self._cache[oid] = new_ref = new_oid, classmeta
        return CopyReference(new_ref)

    def readPickle(self, pickle):
        unpickler = self._get_unpickler(pickle)
        classmeta = unpickler.load()
        state = unpickler.load()
        return classmeta, state

class CopyObjectWriter(ObjectWriter):

    def _persistent_id(self, obj):
        if isinstance(obj, CopyReference):
            return obj.ref
        else:
            return super(CopyObjectWriter, self)._persistent_id(obj)

class ObjectCopier:

    def __init__(self, jar, storage, created):
        self.oids = {}
        self._reader = CopyObjectReader(storage, created, self.oids)
        self._writer = CopyObjectWriter(jar)

    def copy(self, pickle):
        classmeta, state = self._reader.readPickle(pickle)
        return self._writer._dump(classmeta, state)

def findrefs(p):
    f = StringIO(p)
    u = cPickle.Unpickler(f)
    u.persistent_load = L = []
    u.noload()
    try:
        u.noload()
    except EOFError, err:
        logging.warn("ZODB: Bad pickled: %s", err)
    # Iterator over L and convert persistent references to simple oids.
    oids = []
    for ref in L:
        if isinstance(ref, TupleType):
            oids.append(ref[0])
        else:
            oids.append(ref)
    return oids


=== Added File Zope3/src/zodb/timestamp.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
# 
##############################################################################
import time

from _TimeStamp import TimeStamp

def newTimeStamp(prev=None):
    ts = timeStampFromTime(time.time())
    if prev is not None:
        ts = ts.laterThan(prev)
    return ts

def timeStampFromTime(t):
    args = time.gmtime(t)[:5] + (t % 60,)
    return TimeStamp(*args)


=== Added File Zope3/src/zodb/utils.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
# 
##############################################################################
from zodb.timestamp import TimeStamp
import struct
import time

z64 = "\0" * 8

def p64(v):
    """Pack an integer or long into a 8-byte string"""
    return struct.pack(">Q", v)

def u64(v):
    """Unpack an 8-byte string into a 64-bit long integer."""
    return struct.unpack(">Q", v)[0]

def cp(f1, f2, l):
    read = f1.read
    write = f2.write
    n = 8192
    
    while l > 0:
        if n > l:
            n = l
        d = read(n)
        if not d:
            break
        write(d)
        l = l - len(d)

try:
    from sets import Set
except ImportError:
    # This must be Python 2.2, which doesn't have a standard sets module.
    # ZODB needs only a very limited subset of the Set API.
    class Set(dict):
        def add(self, o):
            self[o] = 1
        def __ior__(self, other):
            if not isinstance(other, Set):
                return NotImplemented
            self.update(other)
            return self


=== Added File Zope3/src/zodb/winlock.c ===
/*****************************************************************************

  Copyright (c) 2001, 2002 Zope Corporation and Contributors.
  All Rights Reserved.
  
  This software is subject to the provisions of the Zope Public License,
  Version 2.0 (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
  
 ****************************************************************************/
static char winlock_doc_string[] = 
"Lock files on Windows."
"\n"
"$Id: winlock.c,v 1.1.2.1 2002/12/23 19:30:43 jim Exp $\n";

#include "Python.h"

static PyObject *Error;

#ifdef MS_WIN32

#include <windows.h>
#include <io.h>

static PyObject *	
winlock(PyObject *ignored, PyObject *args)
{
  int fileno;
  long h, ol, oh, ll, lh;
  
  if (! PyArg_ParseTuple(args, "illll", &fileno, &ol, &oh, &ll, &lh))
    return NULL;

  if ((h=_get_osfhandle(fileno))==-1) {
    PyErr_SetString(Error, "_get_osfhandle failed");
    return NULL;
  }
  if (LockFile((HANDLE)h, ol, oh, ll, lh)) {
    Py_INCREF(Py_None);
    return Py_None;
  }
  PyErr_SetObject(Error, PyInt_FromLong(GetLastError()));
  return NULL;
}

static struct PyMethodDef methods[] = {
  {"LockFile",	(PyCFunction)winlock,	1,
   "LockFile(fileno, offsetLow, offsetHigh, lengthLow, lengthHigh ) -- "
   "Lock the file associated with fileno"},
  {NULL,		NULL}		/* sentinel */
};
#else

static struct PyMethodDef methods[] = {
  {NULL,		NULL}		/* sentinel */
};

#endif

/* Initialization function for the module (*must* be called initcStringIO) */

#ifndef DL_EXPORT	/* declarations for DLL import/export */
#define DL_EXPORT(RTYPE) RTYPE
#endif
DL_EXPORT(void)
initwinlock(void) {
  PyObject *m, *d;

  if (!(Error=PyString_FromString("winlock.error"))) 
      return;

  /* Create the module and add the functions */
  m = Py_InitModule4("winlock", methods, winlock_doc_string, (PyObject*)NULL,
		     PYTHON_API_VERSION);

  d = PyModule_GetDict(m);
  PyDict_SetItemString(d, "error", Error);
}


=== Added File Zope3/src/zodb/ztransaction.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
from transaction import set_factory
from transaction.txn import Transaction as BaseTransaction

from zodb.interfaces import ITransaction, ITransactionAttrs

class Transaction(BaseTransaction):

    __implements__ = ITransaction, ITransactionAttrs

    user = ""
    description = ""
    _extension = None

    def __init__(self, manager=None, parent=None,
                 user=None, description=None):
        super(Transaction, self).__init__(manager, parent)
        if user is not None:
            self.user = user
        if description is not None:
            self.description = description

    def note(self, text):
        if self.description:
            self.description = "%s\n\n%s" % (self.description, text)
        else: 
            self.description = text
    
    def setUser(self, user_name, path='/'):
        self.user = "%s %s" % (path, user_name)

    def setExtendedInfo(self, name, value):
        if self._extension is None:
            self._extension = {}
        self._extension[name] = value

set_factory(Transaction)