386 lines
12 KiB
Python
386 lines
12 KiB
Python
""" This module reimplements Python's file I/O mechanisms using Panda
|
|
constructs. This enables Python to interface more easily with Panda's
|
|
virtual file system, and it also better-supports Panda's
|
|
SIMPLE_THREADS model, by avoiding blocking all threads while waiting
|
|
for I/O to complete. """
|
|
|
|
__all__ = [
|
|
'open', 'listdir', 'walk', 'join',
|
|
'isfile', 'isdir', 'exists', 'lexists', 'getmtime', 'getsize',
|
|
'execfile',
|
|
]
|
|
|
|
from panda3d import core
|
|
import sys
|
|
import os
|
|
import io
|
|
import encodings
|
|
from posixpath import join
|
|
|
|
_vfs = core.VirtualFileSystem.getGlobalPtr()
|
|
|
|
if sys.version_info < (3, 0):
|
|
# Python 3 defines these subtypes of IOError, but Python 2 doesn't.
|
|
FileNotFoundError = IOError
|
|
IsADirectoryError = IOError
|
|
FileExistsError = IOError
|
|
PermissionError = IOError
|
|
|
|
unicodeType = unicode
|
|
strType = str
|
|
else:
|
|
unicodeType = str
|
|
strType = ()
|
|
|
|
|
|
def open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True):
|
|
if sys.version_info >= (3, 0):
|
|
# Python 3 is much stricter than Python 2, which lets
|
|
# unknown flags fall through.
|
|
for ch in mode:
|
|
if ch not in 'rwxabt+U':
|
|
raise ValueError("invalid mode: '%s'" % (mode))
|
|
|
|
creating = 'x' in mode
|
|
writing = 'w' in mode
|
|
appending = 'a' in mode
|
|
updating = '+' in mode
|
|
binary = 'b' in mode
|
|
universal = 'U' in mode
|
|
reading = universal or 'r' in mode
|
|
|
|
if binary and 't' in mode:
|
|
raise ValueError("can't have text and binary mode at once")
|
|
|
|
if creating + reading + writing + appending > 1:
|
|
raise ValueError("must have exactly one of create/read/write/append mode")
|
|
|
|
if binary:
|
|
if encoding:
|
|
raise ValueError("binary mode doesn't take an encoding argument")
|
|
if errors:
|
|
raise ValueError("binary mode doesn't take an errors argument")
|
|
if newline:
|
|
raise ValueError("binary mode doesn't take a newline argument")
|
|
|
|
if isinstance(file, core.Istream) or isinstance(file, core.Ostream):
|
|
# If we were given a stream instead of a filename, assign
|
|
# it directly.
|
|
raw = StreamIOWrapper(file)
|
|
raw.mode = mode
|
|
|
|
else:
|
|
vfile = None
|
|
|
|
if isinstance(file, core.VirtualFile):
|
|
# We can also "open" a VirtualFile object for reading.
|
|
vfile = file
|
|
filename = vfile.getFilename()
|
|
elif isinstance(file, unicodeType):
|
|
# If a raw string is given, assume it's an os-specific
|
|
# filename.
|
|
filename = core.Filename.fromOsSpecificW(file)
|
|
elif isinstance(file, strType):
|
|
filename = core.Filename.fromOsSpecific(file)
|
|
else:
|
|
# If a Filename is given, make a writable copy anyway.
|
|
filename = core.Filename(file)
|
|
|
|
if binary or sys.version_info >= (3, 0):
|
|
filename.setBinary()
|
|
else:
|
|
filename.setText()
|
|
|
|
if not vfile:
|
|
vfile = _vfs.getFile(filename)
|
|
|
|
if not vfile:
|
|
if reading:
|
|
raise FileNotFoundError("No such file or directory: '%s'" % (filename))
|
|
|
|
vfile = _vfs.createFile(filename)
|
|
if not vfile:
|
|
raise IOError("Failed to create file: '%s'" % (filename))
|
|
|
|
elif creating:
|
|
# In 'creating' mode, we have to raise FileExistsError
|
|
# if the file already exists. Otherwise, it's the same
|
|
# as 'writing' mode.
|
|
raise FileExistsError("File exists: '%s'" % (filename))
|
|
|
|
elif vfile.isDirectory():
|
|
raise IsADirectoryError("Is a directory: '%s'" % (filename))
|
|
|
|
# Actually open the streams.
|
|
if reading:
|
|
if updating:
|
|
stream = vfile.openReadWriteFile(False)
|
|
else:
|
|
stream = vfile.openReadFile(False)
|
|
|
|
if not stream:
|
|
raise IOError("Could not open %s for reading" % (filename))
|
|
|
|
elif writing or creating:
|
|
if updating:
|
|
stream = vfile.openReadWriteFile(True)
|
|
else:
|
|
stream = vfile.openWriteFile(False, True)
|
|
|
|
if not stream:
|
|
raise IOError("Could not open %s for writing" % (filename))
|
|
|
|
elif appending:
|
|
if updating:
|
|
stream = vfile.openReadAppendFile()
|
|
else:
|
|
stream = vfile.openAppendFile()
|
|
|
|
if not stream:
|
|
raise IOError("Could not open %s for appending" % (filename))
|
|
|
|
else:
|
|
raise ValueError("Must have exactly one of create/read/write/append mode and at most one plus")
|
|
|
|
raw = StreamIOWrapper(stream, needsVfsClose=True)
|
|
raw.mode = mode
|
|
raw.name = vfile.getFilename().toOsSpecific()
|
|
|
|
# If a binary stream was requested, return the stream we've created.
|
|
if binary:
|
|
return raw
|
|
|
|
# If we're in Python 2, we don't decode unicode strings by default.
|
|
if not encoding and sys.version_info < (3, 0):
|
|
return raw
|
|
|
|
line_buffering = False
|
|
if buffering == 1:
|
|
line_buffering = True
|
|
elif buffering == 0:
|
|
raise ValueError("can't have unbuffered text I/O")
|
|
|
|
# Otherwise, create a TextIOWrapper object to wrap it.
|
|
wrapper = io.TextIOWrapper(raw, encoding, errors, newline, line_buffering)
|
|
wrapper.mode = mode
|
|
return wrapper
|
|
|
|
|
|
if sys.version_info < (3, 0):
|
|
# Python 2 had an alias for open() called file().
|
|
__all__.append('file')
|
|
file = open
|
|
|
|
|
|
class StreamIOWrapper(io.IOBase):
|
|
""" This is a file-like object that wraps around a C++ istream and/or
|
|
ostream object. It only deals with binary data; to work with text I/O,
|
|
create an io.TextIOWrapper object around this, or use the open()
|
|
function that is also provided with this module. """
|
|
|
|
def __init__(self, stream, needsVfsClose=False):
|
|
self.__stream = stream
|
|
self.__needsVfsClose = needsVfsClose
|
|
self.__reader = None
|
|
self.__writer = None
|
|
self.__lastWrite = False
|
|
|
|
if isinstance(stream, core.Istream):
|
|
self.__reader = core.StreamReader(stream, False)
|
|
|
|
if isinstance(stream, core.Ostream):
|
|
self.__writer = core.StreamWriter(stream, False)
|
|
self.__lastWrite = True
|
|
if sys.version_info >= (3, 0):
|
|
# In Python 3, we use appendData, which only accepts bytes.
|
|
self.__write = self.__writer.appendData
|
|
else:
|
|
# In Python 2.7, we also accept unicode objects, which are
|
|
# implicitly converted to C++ strings.
|
|
self.__write = self.__writer.write
|
|
|
|
def __repr__(self):
|
|
s = "<direct.stdpy.file.StreamIOWrapper"
|
|
if hasattr(self, 'name'):
|
|
s += " name='%s'" % (self.name)
|
|
if hasattr(self, 'mode'):
|
|
s += " mode='%s'" % (self.mode)
|
|
s += ">"
|
|
return s
|
|
|
|
def readable(self):
|
|
return self.__reader is not None
|
|
|
|
def writable(self):
|
|
return self.__writer is not None
|
|
|
|
def close(self):
|
|
if self.__needsVfsClose:
|
|
if self.__reader and self.__writer:
|
|
_vfs.closeReadWriteFile(self.__stream)
|
|
elif self.__reader:
|
|
_vfs.closeReadFile(self.__stream)
|
|
else: # self.__writer:
|
|
_vfs.closeWriteFile(self.__stream)
|
|
|
|
self.__needsVfsClose = False
|
|
|
|
self.__stream = None
|
|
self.__reader = None
|
|
self.__writer = None
|
|
|
|
def flush(self):
|
|
if self.__writer:
|
|
self.__stream.clear() # clear eof flag
|
|
self.__stream.flush()
|
|
|
|
def read(self, size=-1):
|
|
if not self.__reader:
|
|
if not self.__writer:
|
|
# The stream is not even open at all.
|
|
raise ValueError("I/O operation on closed file")
|
|
|
|
# The stream is open only in write mode.
|
|
raise IOError("Attempt to read from write-only stream")
|
|
|
|
self.__stream.clear() # clear eof flag
|
|
self.__lastWrite = False
|
|
if size is not None and size >= 0:
|
|
result = self.__reader.extractBytes(size)
|
|
else:
|
|
# Read to end-of-file.
|
|
result = b''
|
|
while not self.__stream.eof():
|
|
result += self.__reader.extractBytes(512)
|
|
return result
|
|
|
|
read1 = read
|
|
|
|
def readline(self, size=-1):
|
|
if not self.__reader:
|
|
if not self.__writer:
|
|
# The stream is not even open at all.
|
|
raise ValueError("I/O operation on closed file")
|
|
|
|
# The stream is open only in write mode.
|
|
raise IOError("Attempt to read from write-only stream")
|
|
|
|
self.__stream.clear() # clear eof flag
|
|
self.__lastWrite = False
|
|
return self.__reader.readline()
|
|
|
|
def seek(self, offset, whence = 0):
|
|
if self.__stream:
|
|
self.__stream.clear() # clear eof flag
|
|
if self.__reader:
|
|
self.__stream.seekg(offset, whence)
|
|
if self.__writer:
|
|
self.__stream.seekp(offset, whence)
|
|
|
|
def tell(self):
|
|
if self.__lastWrite:
|
|
if self.__writer:
|
|
return self.__stream.tellp()
|
|
else:
|
|
if self.__reader:
|
|
return self.__stream.tellg()
|
|
raise ValueError("I/O operation on closed file")
|
|
|
|
def write(self, b):
|
|
if not self.__writer:
|
|
if not self.__reader:
|
|
# The stream is not even open at all.
|
|
raise ValueError("I/O operation on closed file")
|
|
|
|
# The stream is open only in read mode.
|
|
raise IOError("Attempt to write to read-only stream")
|
|
|
|
self.__stream.clear() # clear eof flag
|
|
self.__write(b)
|
|
self.__lastWrite = True
|
|
|
|
def writelines(self, lines):
|
|
if not self.__writer:
|
|
if not self.__reader:
|
|
# The stream is not even open at all.
|
|
raise ValueError("I/O operation on closed file")
|
|
|
|
# The stream is open only in read mode.
|
|
raise IOError("Attempt to write to read-only stream")
|
|
|
|
self.__stream.clear() # clear eof flag
|
|
for line in lines:
|
|
self.__write(line)
|
|
self.__lastWrite = True
|
|
|
|
|
|
def listdir(path):
|
|
""" Implements os.listdir over vfs. """
|
|
files = []
|
|
dirlist = _vfs.scanDirectory(core.Filename.fromOsSpecific(path))
|
|
if dirlist is None:
|
|
raise OSError("No such file or directory: '%s'" % (path))
|
|
|
|
for file in dirlist:
|
|
files.append(file.getFilename().getBasename())
|
|
return files
|
|
|
|
def walk(top, topdown = True, onerror = None, followlinks = True):
|
|
""" Implements os.walk over vfs.
|
|
|
|
Note: we don't support onerror or followlinks; errors are ignored
|
|
and links are always followed. """
|
|
|
|
dirnames = []
|
|
filenames = []
|
|
|
|
dirlist = _vfs.scanDirectory(top)
|
|
if dirlist:
|
|
for file in dirlist:
|
|
if file.isDirectory():
|
|
dirnames.append(file.getFilename().getBasename())
|
|
else:
|
|
filenames.append(file.getFilename().getBasename())
|
|
|
|
if topdown:
|
|
yield (top, dirnames, filenames)
|
|
|
|
for dir in dirnames:
|
|
next = join(top, dir)
|
|
for tuple in walk(next, topdown = topdown):
|
|
yield tuple
|
|
|
|
if not topdown:
|
|
yield (top, dirnames, filenames)
|
|
|
|
def isfile(path):
|
|
return _vfs.isRegularFile(core.Filename.fromOsSpecific(path))
|
|
|
|
def isdir(path):
|
|
return _vfs.isDirectory(core.Filename.fromOsSpecific(path))
|
|
|
|
def exists(path):
|
|
return _vfs.exists(core.Filename.fromOsSpecific(path))
|
|
|
|
def lexists(path):
|
|
return _vfs.exists(core.Filename.fromOsSpecific(path))
|
|
|
|
def getmtime(path):
|
|
file = _vfs.getFile(core.Filename.fromOsSpecific(path), True)
|
|
if not file:
|
|
raise os.error
|
|
return file.getTimestamp()
|
|
|
|
def getsize(path):
|
|
file = _vfs.getFile(core.Filename.fromOsSpecific(path), True)
|
|
if not file:
|
|
raise os.error
|
|
return file.getFileSize()
|
|
|
|
def execfile(path, globals=None, locals=None):
|
|
file = _vfs.getFile(core.Filename.fromOsSpecific(path), True)
|
|
if not file:
|
|
raise os.error
|
|
|
|
data = file.readFile(False)
|
|
exec(data, globals, locals)
|