""" 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__ = [ 'file', 'open', 'listdir', 'walk', 'join', 'isfile', 'isdir', 'exists', 'lexists', 'getmtime', 'getsize', 'execfile', ] from pandac import PandaModules as pm import types _vfs = pm.VirtualFileSystem.getGlobalPtr() class file: def __init__(self, filename, mode = 'r', bufsize = None, autoUnwrap = False): self.__stream = None self.__needsVfsClose = False self.__reader = None self.__writer = None self.closed = True self.encoding = None self.errors = None self.__lastWrite = False self.mode = mode self.name = None self.filename = None self.newlines = None self.softspace = False readMode = False writeMode = False if isinstance(filename, pm.Istream) or isinstance(filename, pm.Ostream): # If we were given a stream instead of a filename, assign # it directly. self.__stream = filename readMode = isinstance(filename, pm.Istream) writeMode = isinstance(filename, pm.Ostream) elif isinstance(filename, pm.VirtualFile): # We can also "open" a VirtualFile object for reading. self.__stream = filename.openReadFile(autoUnwrap) if not self.__stream: message = 'Could not read virtual file %s' % (repr(filename)) raise IOError, message self.__needsVfsClose = True readMode = True else: # Otherwise, we must have been given a filename. Open it. if isinstance(filename, types.StringTypes): # If a raw string is given, assume it's an os-specific # filename. filename = pm.Filename.fromOsSpecific(filename) else: # If a Filename is given, make a writable copy anyway. filename = pm.Filename(filename) self.filename = filename self.name = filename.toOsSpecific() binary = False if 'b' in mode: # Strip 'b'. This means a binary file. i = mode.index('b') mode = mode[:i] + mode[i + 1:] binary = True if 'U' in mode: # Strip 'U'. We don't use it; universal-newline support # is built into Panda, and can't be changed at runtime. i = mode.index('U') mode = mode[:i] + mode[i + 1:] binary = False if mode == '': mode = 'r' # Per Python docs, we insist this is true. assert mode[0] in 'rwa' if binary: filename.setBinary() else: filename.setText() # Actually open the streams. if mode == 'w': self.__stream = _vfs.openWriteFile(filename, autoUnwrap, True) if not self.__stream: message = 'Could not open %s for writing' % (filename) raise IOError, message writeMode = True elif mode == 'a': self.__stream = _vfs.openAppendFile(filename) if not self.__stream: message = 'Could not open %s for writing' % (filename) raise IOError, message writeMode = True elif mode == 'w+': self.__stream = _vfs.openReadWriteFile(filename, True) if not self.__stream: message = 'Could not open %s for writing' % (filename) raise IOError, message readMode = True writeMode = True elif mode == 'a+': self.__stream = _vfs.openReadAppendFile(filename) if not self.__stream: message = 'Could not open %s for writing' % (filename) raise IOError, message readMode = True writeMode = True elif mode == 'r+': self.__stream = _vfs.openReadWriteFile(filename, False) if not self.__stream: message = 'Could not open %s for writing' % (filename) raise IOError, message readMode = True writeMode = True elif mode == 'r': self.__stream = _vfs.openReadFile(filename, autoUnwrap) if not self.__stream: if not _vfs.exists(filename): message = 'No such file: %s' % (filename) else: message = 'Could not open %s for reading' % (filename) raise IOError, message readMode = True self.__needsVfsClose = True if readMode: self.__reader = pm.StreamReader(self.__stream, False) if writeMode: self.__writer = pm.StreamWriter(self.__stream, False) self.__lastWrite = True def __del__(self): self.close() 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.__needsVfsClose = False self.__reader = None self.__writer = None def flush(self): if self.__stream: self.__stream.clear() # clear eof flag self.__stream.flush() def __iter__(self): return self def next(self): line = self.readline() if line: return line raise StopIteration def read(self, size = -1): if not self.__reader: if not self.__writer: # The stream is not even open at all. message = 'I/O operation on closed file' raise ValueError, message # The stream is open only in write mode. message = 'Attempt to read from write-only stream' raise IOError, message self.__stream.clear() # clear eof flag self.__lastWrite = False if size >= 0: result = self.__reader.extractBytes(size) else: # Read to end-of-file. result = '' while not self.__stream.eof(): result += self.__reader.extractBytes(1024) return result def readline(self, size = -1): if not self.__reader: if not self.__writer: # The stream is not even open at all. message = 'I/O operation on closed file' raise ValueError, message # The stream is open only in write mode. message = 'Attempt to read from write-only stream' raise IOError, message self.__stream.clear() # clear eof flag self.__lastWrite = False return self.__reader.readline() def readlines(self, sizehint = -1): lines = [] line = self.readline() while line: lines.append(line) line = self.readline() return lines xreadlines = readlines 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() message = 'I/O operation on closed file' raise ValueError, message def truncate(self): """ Sorry, this isn't supported by Panda's low-level I/O, because it isn't supported by the standard C++ library. """ raise NotImplementedError def write(self, str): if not self.__writer: if not self.__reader: # The stream is not even open at all. message = 'I/O operation on closed file' raise ValueError, message # The stream is open only in read mode. message = 'Attempt to write to read-only stream' raise IOError, message self.__stream.clear() # clear eof flag self.__writer.appendData(str) self.__lastWrite = True def writelines(self, lines): if not self.__writer: if not self.__reader: # The stream is not even open at all. message = 'I/O operation on closed file' raise ValueError, message # The stream is open only in read mode. message = 'Attempt to write to read-only stream' raise IOError, message self.__stream.clear() # clear eof flag for line in lines: self.__writer.appendData(line) self.__lastWrite = True def __enter__(self): return self def __exit__(self, t, v, tb): self.close() open = file def listdir(path): """ Implements os.listdir over vfs. """ files = [] dirlist = _vfs.scanDirectory(pm.Filename.fromOsSpecific(path)) if dirlist is None: message = 'No such file or directory: %s' % (path) raise OSError, message 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 join(path, *args): for part in args: if part == '': continue if part.startswith('/'): path = part elif path.endswith('/'): path = path + part else: path = '/'.join((path, part)) return path def isfile(path): return _vfs.isRegularFile(pm.Filename.fromOsSpecific(path)) def isdir(path): return _vfs.isDirectory(pm.Filename.fromOsSpecific(path)) def exists(path): return _vfs.exists(pm.Filename.fromOsSpecific(path)) def lexists(path): return _vfs.exists(pm.Filename.fromOsSpecific(path)) def getmtime(path): file = _vfs.getFile(pm.Filename.fromOsSpecific(path), True) if not file: raise os.error return file.getTimestamp() def getsize(path): file = _vfs.getFile(pm.Filename.fromOsSpecific(path), True) if not file: raise os.error return file.getFileSize() def execfile(path, globals=None, locals=None): file = _vfs.getFile(pm.Filename.fromOsSpecific(path), True) if not file: raise os.error data = file.readFile(False) exec(data, globals, locals)