• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Products.TemporaryFolder / 4062008799

pending completion
4062008799

push

github

GitHub
Merge pull request #19 from zopefoundation/config-with-zope-product-template-84aa4b80

1 of 34 branches covered (2.94%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 1 file covered. (100.0%)

95 of 228 relevant lines covered (41.67%)

0.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

22.04
/src/Products/TemporaryFolder/mount.py
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
"""Mounted database support
1✔
15
"""
16

17
import logging
1✔
18
import threading
1✔
19
import time
1✔
20

21
import persistent
1✔
22
from Acquisition import Implicit
1✔
23
from Acquisition import ImplicitAcquisitionWrapper
1✔
24
from Acquisition import aq_base
1✔
25
from ZODB.POSException import StorageError
1✔
26

27

28
logger = logging.getLogger('ZODB.Mount')
1✔
29

30
# dbs is a holder for all DB objects, needed to overcome
31
# threading issues.  It maps connection params to a DB object
32
# and a mapping of mount points.
33
dbs = {}
1✔
34

35
# dblock is locked every time dbs is accessed.
36
dblock = threading._allocate_lock()
1✔
37

38

39
class MountedStorageError(StorageError):
1✔
40
    """Unable to access mounted storage."""
41

42

43
def parentClassFactory(jar, module, name):
1✔
44
    # Use the class factory from the parent database.
45
    parent_conn = getattr(jar, '_mount_parent_jar', None)
×
46
    parent_db = getattr(parent_conn, '_db', None)
×
47
    if parent_db is None:
×
48
        _globals = {}
×
49
        _silly = ('__doc__',)
×
50
        return getattr(__import__(
×
51
            module, _globals, _globals, _silly), name)
52
    else:
53
        return parent_db.classFactory(parent_conn, module, name)
×
54

55

56
class MountPoint(persistent.Persistent, Implicit):
1✔
57
    """The base class for a Zope object which, when traversed,
58
    accesses a different database.
59
    """
60

61
    # Default values for non-persistent variables.
62
    _v_db = None
1✔
63
    _v_data = None
1✔
64
    _v_connect_error = None
1✔
65

66
    def __init__(self, path, params=None, classDefsFromRoot=None):
1✔
67
        """
68
        @arg path The path within the mounted database from which
69
        to derive the root.
70

71
        @arg params The parameters used to connect to the database.
72
        No particular format required.
73
        If there is more than one mount point referring to a
74
        database, MountPoint will detect the matching params
75
        and use the existing database.  Include the class name of
76
        the storage.  For example,
77
        ZEO params might be "ZODB.ZEOClient localhost 1081".
78
        """
79
        # The only reason we need a __mountpoint_id is to
80
        # be sure we don't close a database prematurely when
81
        # it is mounted more than once and one of the points
82
        # is unmounted.
83
        self.__mountpoint_id = f'{id(self)}_{time.time():f}'
1✔
84
        if params is None:
1!
85
            # We still need something to use as a hash in
86
            # the "dbs" dictionary.
87
            params = self.__mountpoint_id
1✔
88
        self._params = repr(params)
1✔
89
        self._path = path
1✔
90

91
    def _createDB(self):
1✔
92
        """Gets the database object, usually by creating a Storage object
93
        and returning ZODB.DB(storage).
94
        """
95
        raise NotImplementedError
96

97
    def _getDB(self):
1✔
98
        """Creates or opens a DB object.
99
        """
100
        newMount = 0
×
101
        with dblock:
×
102
            params = self._params
×
103
            dbInfo = dbs.get(params, None)
×
104
            if dbInfo is None:
×
105
                logger.info('Opening database for mounting: %s', params)
×
106
                db = self._createDB()
×
107
                newMount = 1
×
108
                dbs[params] = (db, {self.__mountpoint_id: 1})
×
109
            else:
110
                db, mounts = dbInfo
×
111
                # Be sure this object is in the list of mount points.
112
                if self.__mountpoint_id not in mounts:
×
113
                    newMount = 1
×
114
                    mounts[self.__mountpoint_id] = 1
×
115
            self._v_db = db
×
116
        return db, newMount
×
117

118
    def _getMountpointId(self):
1✔
119
        return self.__mountpoint_id
×
120

121
    def _getMountParams(self):
1✔
122
        return self._params
×
123

124
    def __repr__(self):
1✔
125
        return f'{self.__class__.__name__}({self._path!r}, {self._params})'
×
126

127
    def _openMountableConnection(self, parent):
1✔
128
        # Opens a new connection to the database.
129
        db = self._v_db
×
130
        if db is None:
×
131
            self._v_close_db = 0
×
132
            db, newMount = self._getDB()
×
133
        else:
134
            newMount = 0
×
135
        jar = getattr(self, '_p_jar', None)
×
136
        if jar is None:
×
137
            # Get _p_jar from parent.
138
            self._p_jar = jar = parent._p_jar
×
139
        conn = db.open()
×
140

141
        # Add an attribute to the connection which
142
        # makes it possible for us to find the primary
143
        # database connection.  See ClassFactoryForMount().
144
        conn._mount_parent_jar = jar
×
145

146
        mcc = MountedConnectionCloser(self, conn)
×
147
        jar.onCloseCallback(mcc)
×
148
        return conn, newMount, mcc
×
149

150
    def _getObjectFromConnection(self, conn):
1✔
151
        obj = self._getMountRoot(conn.root())
×
152
        data = aq_base(obj)
×
153
        # Store the data object in a tuple to hide from acquisition.
154
        self._v_data = (data,)
×
155
        return data
×
156

157
    def _getOrOpenObject(self, parent):
1✔
158
        t = self._v_data
×
159
        if t is None:
×
160
            self._v_connect_error = None
×
161
            conn = None
×
162
            newMount = 0
×
163
            mcc = None
×
164
            try:
×
165
                conn, newMount, mcc = self._openMountableConnection(parent)
×
166
                data = self._getObjectFromConnection(conn)
×
167
            except Exception:
×
168
                # Possibly broken database.
169
                if mcc is not None:
×
170
                    # Note that the next line may be a little rash--
171
                    # if, for example, a working database throws an
172
                    # exception rather than wait for a new connection,
173
                    # this will likely cause the database to be closed
174
                    # prematurely.  Perhaps DB.py needs a
175
                    # countActiveConnections() method.
176
                    mcc.setCloseDb()
×
177
                logger.warning('Failed to mount database. %s (%s)',
×
178
                               exc_info=True)
179
                raise
×
180
            if newMount:
×
181
                try:
×
182
                    id = data.getId()
×
183
                except Exception:
×
184
                    id = '???'  # data has no getId() method.  Bad.
×
185
                p = '/'.join(parent.getPhysicalPath() + (id,))
×
186
                logger.info('Mounted database %s at %s',
×
187
                            self._getMountParams(), p)
188
        else:
189
            data = t[0]
×
190

191
        return data.__of__(parent)
×
192

193
    def __of__(self, parent):
1✔
194
        # Accesses the database, returning an acquisition
195
        # wrapper around the connected object rather than around self.
196
        try:
×
197
            return self._getOrOpenObject(parent)
×
198
        except Exception:
×
199
            return ImplicitAcquisitionWrapper(self, parent)
×
200

201
    def _test(self, parent):
1✔
202
        """Tests the database connection.
203
        """
204
        self._getOrOpenObject(parent)
×
205
        return 1
×
206

207
    def _getMountRoot(self, root):
1✔
208
        """Gets the object to be mounted.
209
        Can be overridden to provide different behavior.
210
        """
211
        try:
×
212
            app = root['Application']
×
213
        except Exception:
×
214
            raise MountedStorageError(
×
215
                "No 'Application' object exists in the mountable database.")
216
        try:
×
217
            return app.unrestrictedTraverse(self._path)
×
218
        except Exception:
×
219
            raise MountedStorageError(
×
220
                "The path '%s' was not found in the mountable database."
221
                % self._path)
222

223

224
class MountedConnectionCloser:
1✔
225
    """Closes the connection used by the mounted database
226
    while performing other cleanup.
227
    """
228
    close_db = 0
1✔
229

230
    def __init__(self, mountpoint, conn):
1✔
231
        # conn is the child connection.
232
        self.mp = mountpoint
×
233
        self.conn = conn
×
234

235
    def setCloseDb(self):
1✔
236
        self.close_db = 1
×
237

238
    def __call__(self):
1✔
239
        # The onCloseCallback handler.
240
        # Closes a single connection to the database
241
        # and possibly the database itself.
242
        conn = self.conn
×
243
        close_db = 0
×
244
        if conn is not None:
×
245
            mp = self.mp
×
246
            # Remove potential circular references.
247
            self.conn = None
×
248
            self.mp = None
×
249
            # Detect whether we should close the database.
250
            close_db = self.close_db
×
251
            t = mp.__dict__.get('_v_data', None)
×
252
            if t is not None:
×
253
                del mp.__dict__['_v_data']
×
254
                data = t[0]
×
255
                if not close_db and data.__dict__.get(
×
256
                        '_v__object_deleted__', 0):
257
                    # This mount point has been deleted.
258
                    del data.__dict__['_v__object_deleted__']
×
259
                    close_db = 1
×
260
            # Close the child connection.
261
            try:
×
262
                del conn._mount_parent_jar
×
263
            except Exception:
×
264
                pass
×
265
            conn.close()
×
266

267
        if close_db:
×
268
            # Stop using this database. Close it if no other
269
            # MountPoint is using it.
270
            with dblock:
×
271
                params = mp._getMountParams()
×
272
                mp._v_db = None
×
273
                if params in dbs:
×
274
                    dbInfo = dbs[params]
×
275
                    db, mounts = dbInfo
×
276
                    try:
×
277
                        del mounts[mp._getMountpointId()]
×
278
                    except Exception:
×
279
                        pass
×
280
                    if len(mounts) < 1:
×
281
                        # No more mount points are using this database.
282
                        del dbs[params]
×
283
                        db.close()
×
284
                        logger.info('Closed database: %s', params)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc