1##############################################################################
2#
3# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
4# All Rights Reserved.
5#
6# This software is subject to the provisions of the Zope Public License,
7# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11# FOR A PARTICULAR PURPOSE
12#
13##############################################################################
14"""Open database and storage from a configuration."""
15import os
16import ZConfig
17import ZODB
18
19try:
20    from cStringIO import StringIO
21except ImportError:
22    # Py3
23    from io import StringIO
24
25
26db_schema_path = os.path.join(ZODB.__path__[0], "config.xml")
27_db_schema = None
28
29s_schema_path = os.path.join(ZODB.__path__[0], "storage.xml")
30_s_schema = None
31
32def getDbSchema():
33    global _db_schema
34    if _db_schema is None:
35        _db_schema = ZConfig.loadSchema(db_schema_path)
36    return _db_schema
37
38def getStorageSchema():
39    global _s_schema
40    if _s_schema is None:
41        _s_schema = ZConfig.loadSchema(s_schema_path)
42    return _s_schema
43
44def databaseFromString(s):
45    """Create a database from a database-configuration string.
46
47    The string must contain one or more :ref:`zodb
48    <database-text-configuration>` sections.
49
50    The database defined by the first section is returned.
51
52    If :ref:`more than one zodb section is provided
53    <multidatabase-text-configuration>`, a multi-database
54    configuration will be created and all of the databases will be
55    available in the returned database's ``databases`` attribute.
56    """
57    return databaseFromFile(StringIO(s))
58
59def databaseFromFile(f):
60    """Create a database from a file object that provides configuration.
61
62    See :func:`databaseFromString`.
63    """
64    config, handle = ZConfig.loadConfigFile(getDbSchema(), f)
65    return databaseFromConfig(config.database)
66
67def databaseFromURL(url):
68    """Load a database from URL (or file name) that provides configuration.
69
70    See :func:`databaseFromString`.
71    """
72    config, handler = ZConfig.loadConfig(getDbSchema(), url)
73    return databaseFromConfig(config.database)
74
75def databaseFromConfig(database_factories):
76    databases = {}
77    first = None
78    for factory in database_factories:
79        db = factory.open(databases)
80        if first is None:
81            first = db
82
83    return first
84
85def storageFromString(s):
86    """Create a storage from a storage-configuration string.
87    """
88    return storageFromFile(StringIO(s))
89
90def storageFromFile(f):
91    """Create a storage from a file object providing storage-configuration.
92    """
93    config, handle = ZConfig.loadConfigFile(getStorageSchema(), f)
94    return storageFromConfig(config.storage)
95
96def storageFromURL(url):
97    """\
98    Create a storage from a URL (or file name) providing storage-configuration.
99    """
100    config, handler = ZConfig.loadConfig(getStorageSchema(), url)
101    return storageFromConfig(config.storage)
102
103def storageFromConfig(section):
104    return section.open()
105
106class BaseConfig(object):
107    """Object representing a configured storage or database.
108
109    Methods:
110
111    open() -- open and return the configured object
112
113    Attributes:
114
115    name   -- name of the storage
116
117    """
118
119    def __init__(self, config):
120        self.config = config
121        self.name = config.getSectionName()
122
123    def open(self, database_name='unnamed', databases=None):
124        """Open and return the storage object."""
125        raise NotImplementedError
126
127class ZODBDatabase(BaseConfig):
128
129    def open(self, databases=None):
130        section = self.config
131        storage = section.storage.open()
132        options = {}
133
134        def _option(name, oname=None):
135            v = getattr(section, name)
136            if v is not None:
137                if oname is None:
138                    oname = name
139                options[oname] = v
140
141        _option('pool_timeout')
142        _option('allow_implicit_cross_references', 'xrefs')
143        _option('large_record_size')
144
145        try:
146            return ZODB.DB(
147                storage,
148                pool_size=section.pool_size,
149                cache_size=section.cache_size,
150                cache_size_bytes=section.cache_size_bytes,
151                historical_pool_size=section.historical_pool_size,
152                historical_cache_size=section.historical_cache_size,
153                historical_cache_size_bytes=section.historical_cache_size_bytes,
154                historical_timeout=section.historical_timeout,
155                database_name=section.database_name or self.name or '',
156                databases=databases,
157                **options)
158        except:
159            storage.close()
160            raise
161
162class MappingStorage(BaseConfig):
163
164    def open(self):
165        from ZODB.MappingStorage import MappingStorage
166        return MappingStorage(self.config.name)
167
168class DemoStorage(BaseConfig):
169
170    def open(self):
171        base = changes = None
172        for factory in self.config.factories:
173            if factory.name == 'changes':
174                changes = factory.open()
175            else:
176                if base is None:
177                    base = factory.open()
178                else:
179                    raise ValueError("Too many base storages defined!")
180
181        from ZODB.DemoStorage import DemoStorage
182        return DemoStorage(self.config.name, base=base, changes=changes)
183
184class FileStorage(BaseConfig):
185
186    def open(self):
187        from ZODB.FileStorage import FileStorage
188        config = self.config
189        options = {}
190        if getattr(config, 'packer', None):
191            packer = config.packer
192            if ':' in packer:
193                m, expr = packer.split(':', 1)
194                m = __import__(m, {}, {}, ['*'])
195                options['packer'] = eval(expr, m.__dict__)
196            else:
197                m, name = config.packer.rsplit('.', 1)
198                m = __import__(m, {}, {}, ['*'])
199                options['packer'] = getattr(m, name)
200
201        for name in ('blob_dir', 'create', 'read_only', 'quota', 'pack_gc',
202                     'pack_keep_old'):
203            v = getattr(config, name, self)
204            if v is not self:
205                options[name] = v
206
207        return FileStorage(config.path, **options)
208
209class BlobStorage(BaseConfig):
210
211    def open(self):
212        from ZODB.blob import BlobStorage
213        base = self.config.base.open()
214        return BlobStorage(self.config.blob_dir, base)
215
216
217class ZEOClient(BaseConfig):
218
219    def open(self):
220        from ZEO.ClientStorage import ClientStorage
221        # config.server is a multikey of socket-connection-address values
222        # where the value is a socket family, address tuple.
223        L = [server.address for server in self.config.server]
224        options = {}
225        if self.config.blob_cache_size is not None:
226            options['blob_cache_size'] = self.config.blob_cache_size
227        if self.config.blob_cache_size_check is not None:
228            options['blob_cache_size_check'] = self.config.blob_cache_size_check
229        if self.config.client_label is not None:
230            options['client_label'] = self.config.client_label
231
232        return ClientStorage(
233            L,
234            blob_dir=self.config.blob_dir,
235            shared_blob_dir=self.config.shared_blob_dir,
236            storage=self.config.storage,
237            cache_size=self.config.cache_size,
238            name=self.config.name,
239            client=self.config.client,
240            var=self.config.var,
241            min_disconnect_poll=self.config.min_disconnect_poll,
242            max_disconnect_poll=self.config.max_disconnect_poll,
243            wait=self.config.wait,
244            read_only=self.config.read_only,
245            read_only_fallback=self.config.read_only_fallback,
246            drop_cache_rather_verify=self.config.drop_cache_rather_verify,
247            username=self.config.username,
248            password=self.config.password,
249            realm=self.config.realm,
250            **options)
251
252class BDBStorage(BaseConfig):
253
254    def open(self):
255        from BDBStorage.BerkeleyBase import BerkeleyConfig
256        storageclass = self.get_storageclass()
257        bconf = BerkeleyConfig()
258        for name in dir(BerkeleyConfig):
259            if name.startswith('_'):
260                continue
261            setattr(bconf, name, getattr(self.config, name))
262        return storageclass(self.config.envdir, config=bconf)
263
264class BDBMinimalStorage(BDBStorage):
265
266    def get_storageclass(self):
267        import BDBStorage.BDBMinimalStorage
268        return BDBStorage.BDBMinimalStorage.BDBMinimalStorage
269
270class BDBFullStorage(BDBStorage):
271
272    def get_storageclass(self):
273        import BDBStorage.BDBFullStorage
274        return BDBStorage.BDBFullStorage.BDBFullStorage
275