Viewing file: __init__.py (18.02 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
""" 4Suite repository low-level driver for ordinary file system storage
Copyright 2004 Fourthought, Inc. (USA). Detailed license and copyright information: http://4suite.org/COPYRIGHT Project home, documentation, distributions: http://4suite.org/ """
import sys, os, types from distutils.dir_util import remove_tree from Ft.Rdf import Model, Statement from Ft.Server.Common import ResourceTypes, AclConstants, Schema from Ft.Server.Server import FtServerServerException, Error from Ft.Server import FTSERVER_NAMESPACE from Ft.Xml.XLink import XLINK_NAMESPACE from Ft.Server.Server.Drivers.FtssDriver import DataStoreContainer
g_typeDirs = {ResourceTypes.RESOURCE_METADATA:'metadata', ResourceTypes.RESOURCE_CONTENT:'content', ResourceTypes.RESOURCE_CACHE:'cache', ResourceTypes.SESSION_DATA:'sessions', } def createFlatFilePath(root, path, t): """ Given the absolute path of a directory on the file system to use as the root container of the repository, plus a repo path and a resource type, returns a file system path corresponding to the given repo path and type. """ if t == ResourceTypes.RESOURCE_CONTENT: path = path[1:] else: # flatten path to just a filename for metadata/cache/sessions path = path.replace('/', '#')
# FIXME: Replace filesystem-unsafe characters in path segments. # (Mainly affects Windows). This cannot be done right now because # FtssDriver and PathImp cannot handle anything the least bit # weird in a repo path, like "<", "&", ":", "%" ...
# Replace path separators as needed if os.sep != '/': path = path.replace('/', os.sep)
# Prepend base dir path = os.path.join(root, g_typeDirs[t], path)
return path
CONTENT_PATH = createFlatFilePath
SYSTEM_RDF_FILE = 'system-rdf' USER_RDF_FILE = 'user-rdf'
def InitializeModule(): """ Post-import hook to initialize module's runtime variables that are not required at import time, but will be needed before the module-level functions are called. """ global DATABASE_DIR global RdfDbm
from Ft import GetConfigVar dbdir = os.path.join(GetConfigVar('LOCALSTATEDIR'), 'FlatFile') dbdir = os.environ.get('FT_DATABASE_DIR', dbdir)
# Normalize path, remove unnecessary slashes DATABASE_DIR = os.path.abspath(dbdir)
# FIXME: l10n if not os.path.isdir(DATABASE_DIR): raise ValueError("Directory %r does not exist" % DATABASE_DIR)
# Incorporate the RDF driver module from Ft.Rdf.Drivers import Dbm as RdfDbm RdfDbm.InitializeModule() return
def Exists(properties): """ See if this repo exists. The return value for this is three states: 1. Everything is present 0. Some things are there -1 Nothing is there. """ InitializeModule() root = os.path.join(DATABASE_DIR, properties['Root'])
systemRdfFile = os.path.join(root,SYSTEM_RDF_FILE) userRdfFile = os.path.join(root,USER_RDF_FILE)
if not os.path.exists(root): return -1 if not os.path.exists(systemRdfFile): return 0 if not os.path.exists(userRdfFile): return 0 return 1
def Initialize(properties): """ Initialize a new copy of the repo. This is not the same as a '4ss_manager init'. This is very raw. The adapter will take care of calling our other interfaces (createContainer, createUser, etc) with all of the information about the rest of the 'init' stuff to do. """
root = os.path.join(DATABASE_DIR, properties['Root'])
systemRdfFile = os.path.join(root, SYSTEM_RDF_FILE) userRdfFile = os.path.join(root, USER_RDF_FILE)
if os.path.isdir(DATABASE_DIR): try: os.makedirs(root) except OSError, e: raise FtServerServerException(Error.UNABLE_TO_INITALIZE_REPO, msg=str(e), traceback="creating root directory") else: raise FtServerServerException(Error.UNABLE_TO_INITALIZE_REPO, msg="Directory %s does not exist" % DATABASE_DIR)
# Create the metadata locations for each type. We split on type now for # a bit more effeciency but we may want to do something later for name, value in g_typeDirs.items(): path = os.path.join(root, value) if not os.path.exists(path): os.mkdir(path)
RdfDbm.CreateDb('rdf:' + systemRdfFile) RdfDbm.CreateDb('rdf:' + userRdfFile)
def Destroy(properties,tryOnly=0): """Completly destroy the repo"""
root = os.path.join(DATABASE_DIR,properties['Root'])
#This is a simple as a rm -rf :) if os.path.exists(root): try: remove_tree(root) except OSError: if not tryOnly: raise
def Maintain(properties): """ Perform any maintenance on the db """ pass
import threading g_fileLocks = {}
def GetLock(fName, typ): if not g_fileLocks.has_key((fName, typ)): g_fileLocks[(fName, typ)] = threading.RLock() return g_fileLocks[(fName, typ)]
class FlatFileContainer(DataStoreContainer): """ Implements repository containers as directories on the underlying file system. Overides _createContainer to attempt an os.mkdir on the filesystem """ def __init__(self,db): """ Used if instanciated directly in order to do initialization """ self._db = db def initialize(self): """ Initialize the table """ pass def _createContainer(self,path,content): """ Overidden to update _created to reflect the newly created container """ path = path.absolutePath cPath = CONTENT_PATH(self._rootDir, path, ResourceTypes.RESOURCE_CONTENT) #Add entry for new container in _children cache self._children[path] = [] #Transaction Caviat #1 (creating a container deleted within the same transaction) #If this container was previously marked for deletion (within the same transaction), #remove from _deleted otherwise add to _created if (path, ResourceTypes.RESOURCE_CONTENT, 1) in self._deleted: del self._deleted[(path, ResourceTypes.RESOURCE_CONTENT, 1)] try: del self._deleted[(path, ResourceTypes.RESOURCE_METADATA, 1)] except KeyError: pass try: del self._deleted[(path, ResourceTypes.RESOURCE_CACHE, 1)] except KeyError: pass else: self._created[(path, ResourceTypes.RESOURCE_CONTENT, 1)] = content return def deleteContainer(self, path): key = (path, ResourceTypes.RESOURCE_CONTENT, 1) if key in self._deleted: raise FtServerServerException(Error.UNKNOWN_PATH, path=path)
if key in self._created: del self._created[key]
self._deleted[key] = 1 return
def fetchChildren(self, parent): """ Returns a list of child uris for the given parent. Parent is a PathImp Needs to hit the file system to do a directory listing of the parent PathImp as a file system path """ parentPath = parent.absolutePath if parentPath not in self._children: cPath = CONTENT_PATH(self._rootDir, parentPath, ResourceTypes.RESOURCE_CONTENT) l = GetLock(parent, ResourceTypes.RESOURCE_CONTENT) l.acquire() try: children = os.listdir(cPath) except: children = [] self._children[parentPath] = children l.release() else: children = self._children[parentPath]
return children def manageChildren(self, parent, child, add=True): """ Adds/remove the child specified by the given path to the parent (parent - a PathImp) Needs to add/remove children from _children (so they are accounted for if accessed within the same transaction) """ if (parent.absolutePath, ResourceTypes.RESOURCE_CONTENT, 1) in self._deleted: #Transaction Caviat #2 (adding children to a container deleted previously within the same transaction) #Check if parent is in _deleted. If so, raise an exception about the parent not existing raise FtServerServerException(Error.INVALID_PATH, path=parent.displayPath, type='Container') children = self.fetchChildren(parent) if add: children.append(child) else: index = children.index(child) del children[index]
class FlatFileDriver(FlatFileContainer): """ The FlatFile Driver will store information in a directory tree. This driver will not support any locking or concurrency. See ConcurrentFlatFileDriver for information on that.
To store information, the flat file driver will create two directories in its root directory. The first will be called 'metadata' and under it will be a picture of the metadata in the tree. The second will be called content and will contain the content of the tree.
In the root, it will also create a file called system-rdf which is the RDF of the system using the Dbm driver of 4RDF. from Ft.Server.Server.Drivers.FlatFile import Also in the Root will be a file called user.rdf which is a second RDF Model with user information. """
def __init__(self, root): if root[0] == '/': root = root[1:] self._rootDir = os.path.join(DATABASE_DIR, root)
self._systemRdfFile = os.path.join(self._rootDir, SYSTEM_RDF_FILE) self._userRdfFile = os.path.join(self._rootDir, USER_RDF_FILE)
self._systemRdfModel = None self._userRdfModel = None self._commited = 0
self._created = {} self._read = {} self._modified = {} self._deleted = {} self._children = {} return
def createFile(self, path, typ, content): """When creating a resource, store the content. """ #Verify that we were given a string object if type(content) != types.StringType: raise TypeError("Content must be a string, not %s" % type(content)) key = (path, typ, 0) if key in self._deleted: #If they recreated it, remove from deleted #Turn it into a modified del self._deleted[key] self._modified[(path, typ)] = content else: self._created[key] = content return
def hasFile(self, path, typ): """See if we have any meta information about this resource""" #Look into created if self._created.has_key((path, typ,0)) or self._created.has_key((path, typ,1)): return 1
#Look into Accessed if (path, typ) in self._read: return 1 #Look into Modified if (path, typ) in self._modified: return 1
#Look into deleted if (path, typ, 0) in self._deleted or (path, typ, 1) in self._deleted: return 0 cPath = CONTENT_PATH(self._rootDir, path, typ) if os.path.exists(cPath): return 1 return 0
def fetchFile(self, path, typ):
#First look in created if self._created.has_key((path, typ,0)): return self._created[(path, typ,0)] if self._created.has_key((path, typ,1)): return self._created[(path, typ,1)]
#Look into the cache if self._read.has_key((path, typ)): return self._read[(path, typ)]
#Look into the modified cache if self._modified.has_key((path, typ)): return self._modified[(path, typ)]
#See if it was deleted if (path, typ, 0) in self._deleted: return None
cPath = CONTENT_PATH(self._rootDir, path, typ) if not os.path.exists(cPath): return None
l = GetLock(path, typ) l.acquire() try: fd = open(cPath, 'rb') #Return the content as a string content = fd.read() fd.close() self._read[(path, typ)] = content return content finally: l.release() return
def updateFile(self, path, typ, content): """Update only the content about this resource"""
#Verify that we were given a string object if type(content) != types.StringType: raise TypeError("Content must be a string, not %s" % type(content))
#See if it was deleted if (path, typ, 0) in self._deleted: return None
#Clean up other caches if self._created.has_key((path, typ,0)): del self._created[(path, typ,0)] if self._created.has_key((path, typ,1)): del self._created[(path, typ,1)]
if self._read.has_key((path, typ)): del self._read[(path, typ)]
if self._modified.has_key((path, typ)): del self._modified[(path, typ)]
self._modified[(path, typ)] = content return 1
def deleteFile(self, path, typ): """Delete an object"""
if self._deleted.has_key((path, typ, 0)): raise FtServerServerException(Error.UNKNOWN_PATH, path=path) if self._created.has_key((path, typ, 0)): del self._created[(path, typ,0)]
if self._read.has_key((path, typ)): del self._read[(path, typ)]
if self._modified.has_key((path, typ)): del self._modified[(path, typ)]
self._deleted[(path, typ, 0)] = 1 return def getSystemModel(self): if not self._systemRdfModel: d = RdfDbm.GetDb('rdf:' + self._systemRdfFile) d.begin() self._systemRdfModel = Model.Model(d) return self._systemRdfModel
def getUserModel(self): if not self._userRdfModel: d = RdfDbm.GetDb('rdf:' + self._userRdfFile) d.begin() self._userRdfModel = Model.Model(d) return self._userRdfModel def commit(self): """Make it so!""" if self._systemRdfModel: self._systemRdfModel._driver.commit() self._systemRdfModel = None if self._userRdfModel: self._userRdfModel._driver.commit() self._userRdfModel = None
#Add any created containers #Containers should be created first (with os.mkdir) since the proper order cannot be guaranteed #/ is the only exception (should be skipped) #Container paths are sorted ensure proper creation order sortedContainerList = [key for key in self._created if key[-1]] sortedContainerList.sort() for path,typ,container in sortedContainerList: if path != "/": cPath = CONTENT_PATH(self._rootDir, path, typ) l = GetLock(path, ResourceTypes.RESOURCE_CONTENT) l.acquire() try: os.mkdir(cPath) except: raise FtServerServerException(Error.NOT_SUPPORTED, reason='Unable to do an os.mkdir on %s (%s) cannot proceed'%(path,cPath)) l.release()
#Add files for (path,typ,container),content in self._created.items(): if not container: cPath = CONTENT_PATH(self._rootDir, path, typ) l = GetLock(path, typ) l.acquire() try: if not os.path.isdir(cPath): fd = open(cPath, 'wb') fd.write(content) fd.close() finally: l.release()
#Add any modified files for (path,typ),content in self._modified.items(): cPath = CONTENT_PATH(self._rootDir, path, typ) l = GetLock(path, typ) l.acquire() try: #Special case (containers) use os.mkdir (so ignore) if not os.path.isdir(cPath): fd = open(cPath, 'wb') fd.write(content) fd.close() finally: l.release()
# Remove any deleted files deleted_files = [ info for info in self._deleted if not info[-1] ] for (path, typ, is_container) in deleted_files: cPath = CONTENT_PATH(self._rootDir, path, typ) l = GetLock(path, typ) l.acquire() try: os.unlink(cPath) finally: l.release() # Containers should be deleted last (with os.rmdir) since the proper # order cannot be guaranteed deleted_dirs = [ info for info in self._deleted if info[-1] ] deleted_dirs.sort() deleted_dirs.reverse() for (path, typ, is_container) in deleted_dirs: cPath = CONTENT_PATH(self._rootDir, path, typ) l = GetLock(path, typ) l.acquire() try: os.rmdir(cPath) finally: l.release()
self._commited = 1 return def rollback(self): """Undo it""" if self._systemRdfModel: self._systemRdfModel._driver.rollback() self._systemRdfModel = None if self._userRdfModel: self._userRdfModel._driver.rollback() self._userRdfModel = None self._commited = 1 return
def Begin(**properties): """Begin a new transaction. Every driver must support this interface. The properties keyword arguments are passed from the config file (or where ever) to the driver. The Begin file is responsible for doing what ever is needed to validate these arguements """ return FlatFileDriver(properties['Root'])
NAME='Flat File'
|