[Zope-CVS] CVS: Products/Ape/lib/apelib/fs - fileops.py:1.1 connection.py:1.3
Shane Hathaway
shane@zope.com
Sat, 24 May 2003 17:51:27 -0400
Update of /cvs-repository/Products/Ape/lib/apelib/fs
In directory cvs.zope.org:/tmp/cvs-serv17360
Modified Files:
connection.py
Added Files:
fileops.py
Log Message:
Abstracted file I/O into a file operations object, with the intent of
later providing alternate implementations such as archive import/export.
=== Added File Products/Ape/lib/apelib/fs/fileops.py ===
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
"""Filesystem I/O abstraction.
$Id: fileops.py,v 1.1 2003/05/24 21:51:26 shane Exp $
"""
import os
import shutil
class StandardFileOperations:
"""Standard filesystem interaction implementation.
Provides the operations needed by FSConnection.
"""
def __init__(self):
self.dirname = os.path.dirname
self.exists = os.path.exists
self.getmtime = os.path.getmtime
self.isdir = os.path.isdir
self.join = os.path.join
self.listdir = os.listdir
self.makedirs = os.makedirs
self.mkdir = os.mkdir
self.remove = os.remove
self.rename = os.rename
self.rmtree = shutil.rmtree
self.split = os.path.split
self.splitext = os.path.splitext
def readfile(self, path, as_text):
f = open(path, as_text and 'rt' or 'rb')
try:
return f.read()
finally:
f.close()
def writefile(self, path, as_text, bytes):
f = open(path, as_text and 'wt' or 'wb')
try:
f.write(bytes)
finally:
f.close()
def canwrite(self, path):
return os.access(path, os.W_OK)
=== Products/Ape/lib/apelib/fs/connection.py 1.2 => 1.3 ===
--- Products/Ape/lib/apelib/fs/connection.py:1.2 Mon Apr 21 13:26:33 2003
+++ Products/Ape/lib/apelib/fs/connection.py Sat May 24 17:51:26 2003
@@ -16,9 +16,7 @@
$Id$
"""
-import os
import re
-from shutil import rmtree
from types import StringType
from apelib.core.interfaces import ITPCConnection
@@ -26,6 +24,7 @@
from interfaces import IFSConnection
from exceptions import FSWriteError
from cache import ShortLivedCache
+from fileops import StandardFileOperations
# Try to decipher this regular expression ;-)
@@ -67,7 +66,8 @@
basepath = ''
- def __init__(self, basepath, metadata_prefix='.', hidden_filenames='_'):
+ def __init__(self, basepath, metadata_prefix='.', hidden_filenames='_',
+ ops=None):
self.basepath = basepath
self.metadata_prefix = metadata_prefix
self.hidden_re = re.compile(hidden_filenames)
@@ -77,6 +77,9 @@
self._pending = {}
self._props_cache = ShortLivedCache()
self._dir_cache = ShortLivedCache()
+ if ops is None:
+ ops = StandardFileOperations()
+ self.ops = ops
def _isLegalFilename(self, fn):
@@ -101,7 +104,7 @@
obj_names = []
trans = {} # { base name -> filename with extension or None }
try:
- fns = os.listdir(path)
+ fns = self.ops.listdir(path)
except OSError:
if ignore_error:
return ([], obj_names, trans)
@@ -131,7 +134,7 @@
res = (filenames, obj_names, trans)
self._dir_cache.set(path, res)
return res
-
+
def _listDirectoryAsMapping(self, path, ignore_error=0):
"""Returns the translated filenames at path.
@@ -158,12 +161,12 @@
if self.basepath:
while subpath.startswith('/') or subpath.startswith('\\'):
subpath = subpath[1:]
- path = os.path.join(self.basepath, subpath)
+ path = self.ops.join(self.basepath, subpath)
else:
# unchanged.
path = subpath
- if not os.path.exists(path):
- dir_path, obj_name = os.path.split(path)
+ if not self.ops.exists(path):
+ dir_path, obj_name = self.ops.split(path)
if '.' not in obj_name:
# This object might have an automatic filename extension.
filenames, obj_names, trans = self._computeDirectoryContents(
@@ -171,7 +174,7 @@
fn = trans.get(obj_name)
if fn is not None:
# Use the filename with an extension.
- path = os.path.join(dir_path, fn)
+ path = self.ops.join(dir_path, fn)
return path
@@ -212,39 +215,30 @@
def readNodeType(self, subpath):
path = self._expandPath(subpath)
- if not os.path.exists(path):
+ if not self.ops.exists(path):
raise NoStateFoundError(subpath)
- return os.path.isdir(path) and 'd' or 'f'
+ return self.ops.isdir(path) and 'd' or 'f'
def readData(self, subpath, allow_missing=0, as_text=0):
path = self._expandPath(subpath)
- isdir = os.path.isdir(path)
+ isdir = self.ops.isdir(path)
# Read either the directory listing or the file contents.
if isdir:
# Return a sequence of object names.
return self._listDirectoryAsMapping(path).values()
- else:
- # Return a string.
- if as_text:
- mode = 'rt'
- else:
- mode = 'rb'
- try:
- f = open(path, mode)
- except IOError:
- if allow_missing:
- return None
- raise
- try:
- return f.read()
- finally:
- f.close()
+ # Return a string.
+ try:
+ return self.ops.readfile(path, as_text)
+ except IOError:
+ if allow_missing:
+ return None
+ raise
def getExtension(self, subpath):
path = self._expandPath(subpath)
- stuff, ext = os.path.splitext(path)
+ stuff, ext = self.ops.splitext(path)
return ext
@@ -260,7 +254,7 @@
maxtime = -1
for p in (path, props, remainder):
try:
- t = os.path.getmtime(p)
+ t = self.ops.getmtime(p)
except OSError:
pass
else:
@@ -273,11 +267,11 @@
def _getPropertyPaths(self, path):
"""Returns the property and remainder paths for a path."""
- if os.path.isdir(path):
- base_fn = os.path.join(path, self.metadata_prefix)
+ if self.ops.isdir(path):
+ base_fn = self.ops.join(path, self.metadata_prefix)
else:
- dirname, filename = os.path.split(path)
- base_fn = os.path.join(dirname, '%s%s.' % (
+ dirname, filename = self.ops.split(path)
+ base_fn = self.ops.join(dirname, '%s%s.' % (
self.metadata_prefix, filename))
return (base_fn + PROPERTIES_EXTENSION, base_fn + REMAINDER_EXTENSION)
@@ -292,29 +286,21 @@
res = {}
try:
- f = open(rem_fn, 'rb')
+ data = self.ops.readfile(rem_fn, 0)
except IOError:
# The remainder file apparently does not exist
pass
else:
- try:
- data = f.read()
- finally:
- f.close()
res[REMAINDER_SECTION] = data
# Note that the remainder can be overridden by the properties
# file. Perhaps that should be prevented in the future.
try:
- f = open(props_fn, 'rt')
+ data = self.ops.readfile(props_fn, 1)
except IOError:
# The properties file apparently does not exist
self._props_cache.set(path, res)
return res
- try:
- data = f.read()
- finally:
- f.close()
pos = 0
prev_section_name = None
@@ -343,11 +329,11 @@
# sections is a mapping.
path = self._expandPath(subpath)
t = sections[NODE_TYPE_SECTION]
- if not os.path.exists(path):
+ if not self.ops.exists(path):
if t == 'd':
- os.mkdir(path)
+ self.ops.mkdir(path)
else:
- fn = os.path.split(path)[1]
+ fn = self.ops.split(path)[1]
if '.' not in fn:
# This object has no extension and doesn't yet exist.
ext = sections.get(SUGGESTED_EXTENSION_SECTION)
@@ -356,13 +342,13 @@
if not ext.startswith('.'):
ext = '.' + ext
p = path + ext
- if not os.path.exists(p):
+ if not self.ops.exists(p):
# No file is in the way.
# Use the suggested extension.
path = p
props_fn, rem_fn = self._getPropertyPaths(path)
- props_f = None
- rem_f = None
+ props_data = ''
+ rem_data = ''
items = sections.items()
items.sort()
try:
@@ -374,66 +360,45 @@
if t == 'd':
# Change the list of subobjects.
self._removeUnlinkedItems(path, data)
- if props_f is None:
- props_f = open(props_fn, 'wt')
- self._writeToProperties(props_f, OBJECT_NAMES_SECTION,
- '\n'.join(data))
+ props_data += self._formatSection(
+ OBJECT_NAMES_SECTION, '\n'.join(data))
self._disableConflictingExtensions(subpath, data)
self._dir_cache.invalidate(path)
else:
# Change the file contents.
- if as_text:
- mode = 'wt'
- else:
- mode = 'wb'
- f = open(path, mode)
- try:
- f.write(data)
- finally:
- f.close()
+ self.ops.writefile(path, as_text, data)
elif name == SUGGESTED_EXTENSION_SECTION:
# This doesn't need to be written.
pass
elif name == REMAINDER_SECTION:
# Write to the remainder file.
- if rem_f is None:
- rem_f = open(rem_fn, 'wb')
- rem_f.write(value)
+ rem_data = value
else:
# Write a metadata section.
- if props_f is None:
- props_f = open(props_fn, 'wt')
- self._writeToProperties(props_f, name, value)
+ props_data += self._formatSection(name, value)
finally:
- self._closeOrDelete(props_f, props_fn)
- self._closeOrDelete(rem_f, rem_fn)
+ self._writeOrRemove(props_fn, 1, props_data)
+ self._writeOrRemove(rem_fn, 0, rem_data)
self._props_cache.invalidate(path)
# The file might be new, so invalidate the directory.
- self._dir_cache.invalidate(os.path.dirname(path))
-
+ self._dir_cache.invalidate(self.ops.dirname(path))
- def _writeToProperties(self, props_f, name, text):
- props_f.write('[%s]\n' % name)
- props_f.write(text.replace('[', '[['))
- if text.endswith('\n'):
- props_f.write('\n')
- else:
- props_f.write('\n\n')
+ def _formatSection(self, name, text):
+ s = '[%s]\n%s\n' % (name, text.replace('[', '[['))
+ if not text.endswith('\n'):
+ s += '\n'
+ return s
- def _closeOrDelete(self, f, fn):
- """Finish writing or remove a file.
- If f was opened, close it. If f was not opened, no file is needed,
- so remove it.
+ def _writeOrRemove(self, fn, as_text, data):
+ """If data is provided, write it. Otherwise remove the file.
"""
- if f is not None:
- f.close()
+ if data:
+ self.ops.writefile(fn, as_text, data)
else:
- try:
- os.remove(fn)
- except OSError:
- pass
+ if self.ops.exists(fn):
+ self.ops.remove(fn)
def _removeUnlinkedItems(self, path, names):
@@ -443,16 +408,16 @@
linked[name] = 1
for fn, obj_name in self._listDirectoryAsMapping(path).items():
if not linked.get(obj_name):
- item_fn = os.path.join(path, fn)
- if os.path.isdir(item_fn):
- rmtree(item_fn)
+ item_fn = self.ops.join(path, fn)
+ if self.ops.isdir(item_fn):
+ self.ops.rmtree(item_fn)
else:
- os.remove(item_fn)
+ self.ops.remove(item_fn)
props_fn, rem_fn = self._getPropertyPaths(item_fn)
- if os.path.exists(props_fn):
- os.remove(props_fn)
- if os.path.exists(rem_fn):
- os.remove(rem_fn)
+ if self.ops.exists(props_fn):
+ self.ops.remove(props_fn)
+ if self.ops.exists(rem_fn):
+ self.ops.remove(rem_fn)
def _disableConflictingExtensions(self, subpath, obj_names):
@@ -490,8 +455,8 @@
non_containers = {}
for subpath, sections in items:
path = self._expandPath(subpath)
- exists = os.path.exists(path)
- if exists and not os.access(path, os.W_OK):
+ exists = self.ops.exists(path)
+ if exists and not self.ops.canwrite(path):
raise FSWriteError(
"Can't get write access to %s" % subpath)
# type must be provided and must always be either 'd' or 'f'.
@@ -500,13 +465,13 @@
raise FSWriteError(
'Data or node type not specified for %s' % subpath)
t = sections[NODE_TYPE_SECTION]
- dir = os.path.dirname(subpath)
+ dir = self.ops.dirname(subpath)
if non_containers.get(dir):
raise FSWriteError(
"Not a directory: %s" % dir)
data, as_text = sections[DATA_SECTION]
if t == 'f':
- if exists and os.path.isdir(path):
+ if exists and self.ops.isdir(path):
raise FSWriteError(
"Can't write file data to directory at %s"
% subpath)
@@ -516,7 +481,7 @@
'Data for a file must be a string at %s'
% subpath)
elif t == 'd':
- if exists and not os.path.isdir(path):
+ if exists and not self.ops.isdir(path):
raise FSWriteError(
"Can't write directory contents to file at %s"
% subpath)
@@ -560,8 +525,8 @@
return self.basepath
def connect(self):
- if not os.path.exists(self.basepath):
- os.makedirs(self.basepath)
+ if not self.ops.exists(self.basepath):
+ self.ops.makedirs(self.basepath)
def begin(self):
self._props_cache.clear()