ProPeler
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
opt
/
cpanel-ccs
/
txdav
/
base
/
datastore
/
Filename :
file.py
back
Copy
# -*- test-case-name: txdav -*- ## # Copyright (c) 2010-2017 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## from __future__ import print_function """ Common utility functions for a file based datastore. """ from zope.interface.declarations import implements import hashlib from twext.python.log import Logger from twext.enterprise.ienterprise import AlreadyFinishedError from txweb2.dav.resource import TwistedGETContentMD5 from txdav.idav import IDataStoreObject from txdav.base.propertystore.base import PropertyName from txdav.xml.element import GETContentType def isValidName(name): """ Determine if the given string is a valid name. i.e. does it conflict with any of the other entities which may be on the filesystem? @param name: a name which might be given to a calendar. """ return not name.startswith(".") def hidden(path): return path.sibling('.' + path.basename()) def writeOperation(thunk): # FIXME: tests def inner(self, *a, **kw): if self._transaction._termination is not None: raise RuntimeError( "%s.%s is a write operation, but transaction already %s" % (self, thunk.__name__, self._transaction._termination)) return thunk(self, *a, **kw) return inner class DataStore(object): """ Generic data store. """ log = Logger() _transactionClass = None # Derived class must set this def __init__(self, path): """ Create a calendar store. @param path: a L{FilePath} pointing at a directory on disk. """ self._path = path # if not path.isdir(): # FIXME: Add DataStoreNotFoundError? # raise NotFoundError("No such data store") def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self._path.path) def newTransaction(self, name='no name'): """ Create a new transaction. @see Transaction """ return self._transactionClass(self) class _CommitTracker(object): """ Diagnostic tool to find transactions that were never committed. """ def __init__(self, name): self.name = name self.done = False self.info = [] def __del__(self): if not self.done and self.info: print("**** UNCOMMITTED TRANSACTION (%s) BEING GARBAGE COLLECTED ****") % ( self.name, ) for info in self.info: print(" "), info print("---- END OF OPERATIONS") class DataStoreTransaction(object): """ In-memory implementation of a data store transaction. """ log = Logger() def __init__(self, dataStore, name): """ Initialize a transaction; do not call this directly, instead call L{CalendarStore.newTransaction}. @param calendarStore: The store that created this transaction. @type calendarStore: L{CalendarStore} """ self._dataStore = dataStore self._termination = None self._operations = [] self._postCommitOperations = [] self._postAbortOperations = [] self._tracker = _CommitTracker(name) def store(self): return self._dataStore def addOperation(self, operation, name): self._operations.append(operation) self._tracker.info.append(name) def _terminate(self, mode): """ Check to see if this transaction has already been terminated somehow, either via committing or aborting, and if not, note that it has been terminated. @param mode: The manner of the termination of this transaction. @type mode: C{str} @raise AlreadyFinishedError: This transaction has already been terminated. """ if self._termination is not None: raise AlreadyFinishedError("already %s" % (self._termination,)) self._termination = mode self._tracker.done = True def abort(self): self._terminate("aborted") for operation in self._postAbortOperations: operation() def commit(self): self._terminate("committed") self.committed = True undos = [] for operation in self._operations: try: undo = operation() if undo is not None: undos.append(undo) except: self.log.debug("Undoing DataStoreTransaction") for undo in undos: try: undo() except: self.log.error("Cannot undo DataStoreTransaction") raise for operation in self._postCommitOperations: operation() def postCommit(self, operation): self._postCommitOperations.append(operation) def postAbort(self, operation): self._postAbortOperations.append(operation) class FileMetaDataMixin(object): implements(IDataStoreObject) def name(self): """ Identify the name of the object @return: the name of this object. @rtype: C{str} """ return self._path.basename() def contentType(self): """ The content type of the object's content. @rtype: L{MimeType} """ try: return self.properties()[PropertyName.fromElement(GETContentType)].mimeType() except KeyError: return None def md5(self): """ The MD5 hex digest of this object's content. @rtype: C{str} """ try: return str(self.properties()[PropertyName.fromElement(TwistedGETContentMD5)]) except KeyError: # FIXME: Strictly speaking we should not need to read the data as the md5 property should always be # present. However, our unit tests use static files for their data store and those currently # do not include the md5 xattr. try: with self._path.open() as f: data = f.read() except IOError: return None md5 = hashlib.md5(data).hexdigest() return md5 def size(self): """ The octet-size of this object's content. @rtype: C{int} """ if self._path.exists(): return int(self._path.getsize()) else: return 0 def created(self): """ The creation date-time stamp of this object. @rtype: C{int} """ if self._path.exists(): return self._path.getmtime() # No creation time on POSIX else: return None def modified(self): """ The last modification date-time stamp of this object. @rtype: C{int} """ if self._path.exists(): return self._path.getmtime() else: return None