[Zope-Checkins] CVS: Products/AdaptableStorage/gateway_fs - FSUserList.py:1.1 FSConnection.py:1.7 public.py:1.4

Shane Hathaway shane@zope.com
Mon, 6 Jan 2003 18:18:14 -0500


Update of /cvs-repository/Products/AdaptableStorage/gateway_fs
In directory cvs.zope.org:/tmp/cvs-serv1755/gateway_fs

Modified Files:
	FSConnection.py public.py 
Added Files:
	FSUserList.py 
Log Message:
- Added a user folder mapper for the filesystem.  SQL version coming
  soon.  (SQL tests fail at the moment.)

- Added unwriteData() to FSConnection.  I may remove it later, since it turned
  out I didn't need it.

- Made configuration errors easier to read.

- Added null implementations of certain interfaces.

- Added a default for FixedClassifier.


=== Added File Products/AdaptableStorage/gateway_fs/FSUserList.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.
#
##############################################################################
"""User list gateway, where the user list is stored in a flat file.

$Id: FSUserList.py,v 1.1 2003/01/06 23:17:41 shane Exp $
"""

from mapper_public import IGateway, RowSequenceSchema


class FSUserList:

    __implements__ = IGateway

    schema = RowSequenceSchema()
    schema.addField('id', 'string', 1)
    schema.addField('password', 'string')
    schema.addField('roles', 'string:list')
    schema.addField('domains', 'string:list')

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

    def getSchema(self):
        return self.schema

    def load(self, event):
        c = self.fs_conn
        p = event.getKeychain()[-1]
        assert c.readNodeType(p) == 'f'
        text = c.readData(p)
        res = []
        for line in text.split('\n'):
            L = line.strip()
            if ':' in L:
                id, password, rolelist, domainlist = L.split(':', 3)
                roles = self._splitList(rolelist)
                domains = self._splitList(domainlist)
                res.append((id, password, roles, domains))
        res.sort()
        return res, text


    def _splitList(self, s):
        return tuple([item.strip() for item in s.split(',') if item])


    def _joinList(self, items):
        for item in items:
            if item.strip() != item:
                raise MappingError(
                    "Leading and trailing whitespace are not allowed in %s"
                    % repr(item))
            item = item.strip()
            if ',' in item or ':' in item or '\n' in item:
                raise MappingError(
                    "Commas, colons, and newlines are not allowed in %s"
                    % repr(item))
        return ','.join(items)


    def store(self, event, state):
        replace_lines = {}
        for id, password, roles, domains in state:
            if ':' in id or '\n' in id:
                raise MappingError('User IDs cannot have colons or newlines')
            if ':' in password or '\n' in password:
                raise MappingError('Passwords cannot have colons or newlines')
            rolelist = self._joinList(roles)
            domainlist = self._joinList(domains)
            to_write = '%s:%s:%s:%s' % (id, password, rolelist, domainlist)
            replace_lines[id] = to_write
        p = event.getKeychain()[-1]
        self.fs_conn.writeNodeType(p, 'f')
        text = self.fs_conn.readData(p, allow_missing=1)
        if text is None:
            text = ''
        new_lines = []
        # Replace / remove users
        for line in text.split('\n'):
            L = line.strip()
            if ':' in L:
                name, stuff = L.split(':', 1)
                replace = replace_lines.get(name, '')
                if replace and replace != L:
                    new_lines.append(replace)
                    del replace_lines[name]
                # else remove the line
            else:
                new_lines.append(line)
        # Append new users
        for line in replace_lines.values():
            new_lines.append(line)
        # Write it
        text = '\n'.join(new_lines)
        self.fs_conn.writeData(p, text)
        serial = list(state)
        serial.sort()
        return text



=== Products/AdaptableStorage/gateway_fs/FSConnection.py 1.6 => 1.7 ===
--- Products/AdaptableStorage/gateway_fs/FSConnection.py:1.6	Tue Dec 31 16:47:44 2002
+++ Products/AdaptableStorage/gateway_fs/FSConnection.py	Mon Jan  6 18:17:41 2003
@@ -26,14 +26,14 @@
 from mapper_public import ITPCConnection
 
 
-# Try to decipher this one ;-)
+# Try to decipher this regular expression ;-)
 # It basically matches "\n[sectionname]...\n", where len(sectionname) > 0.
 section_re = re.compile(r'^\[([^\[\]\n]+)\][^\r\n]*(?:\r\n|\r|\n)',
                       re.MULTILINE)
 
 
-NODE_TYPE_SECTION = '@node_type'
-DATA_SECTION = '@data'
+NODE_TYPE_SECTION = '@node_type'  # Data is 'f' (file) or 'd' (directory)
+DATA_SECTION = '@data'  # Data is a string (file) or list of names (directory)
 
 
 # Write modes
@@ -73,7 +73,7 @@
         self._preserve = {}
 
 
-    def expandPath(self, subpath, check_exists=0):
+    def expandPath(self, subpath):
         if self.basepath:
             while subpath.startswith('/') or subpath.startswith('\\'):
                 subpath = subpath[1:]
@@ -81,8 +81,6 @@
         else:
             # unchanged.
             path = subpath
-        if check_exists:
-            assert os.path.exists(path), '%s does not exist' % path
         return path
 
 
@@ -128,20 +126,32 @@
         self._write(subpath, DATA_SECTION, data)
 
 
+    def unwriteData(self, subpath):
+        m = self._pending
+        sections = m.get(subpath)
+        if sections is not None:
+            if sections.has_key(DATA_SECTION):
+                res = sections[DATA_SECTION]
+                del sections[DATA_SECTION]
+                return res
+        # Nothing was written.
+        return None
+
+
     def readSection(self, subpath, section_name, default=None):
         self.checkSectionName(section_name)
-        path = self.expandPath(subpath, 1)
+        path = self.expandPath(subpath)
         sections = self.getPropertiesFromFile(path)
         return sections.get(section_name, default)
 
 
     def readNodeType(self, subpath):
-        path = self.expandPath(subpath, 1)
+        path = self.expandPath(subpath)
         return os.path.isdir(path) and 'd' or 'f'
 
 
-    def readData(self, subpath):
-        path = self.expandPath(subpath, 1)
+    def readData(self, subpath, allow_missing=0):
+        path = self.expandPath(subpath)
         isdir = os.path.isdir(path)
         # Read either the directory listing or the file contents.
         if isdir:
@@ -153,7 +163,12 @@
             # Return a sequence instead of a string.
             return names
         else:
-            f = open(path, 'rb')
+            try:
+                f = open(path, 'rb')
+            except IOError:
+                if allow_missing:
+                    return None
+                raise
             try:
                 return f.read()
             finally:
@@ -175,9 +190,11 @@
         """Read a properties file next to path."""
         props_fn = self.getPropertiesPath(path)
 
-        if not os.path.exists(props_fn):
+        try:
+            f = open(props_fn, 'rb')
+        except IOError:
+            # The file is presumably nonexistent
             return {}
-        f = open(props_fn, 'rb')
         try:
             data = f.read()
         finally:
@@ -287,6 +304,7 @@
                         'Data for a directory must be a list or tuple at %s'
                         % subpath)
 
+
     def queue(self, subpath, section_name, data, mode=WRITE_UNCONDITIONAL):
         """Queues data to be written at commit time"""
         if mode == WRITE_UNCONDITIONAL:
@@ -310,6 +328,7 @@
                     (subpath, section_name))
         else:
             sections[section_name] = data
+
 
     #
     # ITPCConnection implementation


=== Products/AdaptableStorage/gateway_fs/public.py 1.3 => 1.4 ===
--- Products/AdaptableStorage/gateway_fs/public.py:1.3	Fri Jan  3 17:04:17 2003
+++ Products/AdaptableStorage/gateway_fs/public.py	Mon Jan  6 18:17:41 2003
@@ -25,4 +25,4 @@
 from FSFileData import FSFileData
 from FSProperties import FSProperties
 from FSSectionData import FSSectionData
-
+from FSUserList import FSUserList