VFSImporter

This commit is contained in:
David Rose 2008-11-05 01:09:35 +00:00
parent fdacaa063b
commit 18605b1668

View File

@ -0,0 +1,263 @@
from direct.stdpy.file import open
from pandac.PandaModules import Filename, VirtualFileSystem, VirtualFileMountSystem
import sys
import new
import os
import marshal
import imp
import struct
import __builtin__
__all__ = ['register']
vfs = VirtualFileSystem.getGlobalPtr()
# Possible file types.
FTPythonSource = 0
FTPythonCompiled = 1
FTCompiledModule = 2
pycExtension = 'pyc'
if not __debug__:
# In optimized mode, we actually operate on .pyo files, not .pyc
# files.
pycExtension = 'pyo'
class VFSImporter:
""" This class serves as a Python importer to support loading
Python .py and .pyc/.pyo files from Panda's Virtual File System,
which allows loading Python source files from mounted .mf files
(among other places). """
def __init__(self, path):
self.dir_path = Filename.fromOsSpecific(path)
def find_module(self, fullname):
basename = fullname.split('.')[-1]
path = Filename(self.dir_path, Filename(basename))
# First, look for Python files.
filename = path
filename.setExtension('py')
vfile = vfs.getFile(filename, True)
if vfile:
return VFSLoader(self, vfile, filename, FTPythonSource)
# If there's no .py file, but there's a .pyc file, load that
# anyway.
filename = path
filename.setExtension(pycExtension)
vfile = vfs.getFile(filename, True)
if vfile:
return VFSLoader(self, vfile, filename, FTPythonCompiled)
# Look for a compiled C/C++ module.
for desc in imp.get_suffixes():
if desc[2] != imp.C_EXTENSION:
continue
filename = path
filename.setExtension(desc[0][1:])
vfile = vfs.getFile(filename, True)
if vfile:
return VFSLoader(self, vfile, filename, FTCompiledModule,
desc = desc)
# Finally, consider a package, i.e. a directory containing
# __init__.py.
filename = Filename(path, Filename('__init__.py'))
vfile = vfs.getFile(filename, True)
if vfile:
return VFSLoader(self, vfile, filename, FTPythonSource,
package = True)
filename = Filename(path, Filename('__init__.' + pycExtension))
vfile = vfs.getFile(filename, True)
if vfile:
return VFSLoader(self, vfile, filename, FTPythonCompiled,
package = True)
return None
class VFSLoader:
""" The second part of VFSImporter, this is created for a
particular .py file or directory. """
def __init__(self, importer, vfile, filename, fileType,
desc = None, package = False):
self.importer = importer
self.dir_path = importer.dir_path
self.timestamp = vfile.getTimestamp()
self.filename = filename
self.fileType = fileType
self.desc = desc
self.package = package
def load_module(self, fullname):
if self.fileType == FTCompiledModule:
return self._import_compiled_module(fullname)
code = self._read_code()
if not code:
raise ImportError
mod = sys.modules.setdefault(fullname, new.module(fullname))
mod.__file__ = self.filename.cStr()
mod.__loader__ = self
if self.package:
mod.__path__ = []
exec code in mod.__dict__
return mod
def getdata(self, path):
path = Filename(self.dir_path, Filename.fromOsSpecific(path))
f = open(path, 'rb')
return f.read()
def is_package(self, fullname):
return self.package
def get_code(self, fullname):
return self._read_code()
def get_source(self, fullname):
return self._read_source()
def _read_source(self):
""" Returns the Python source for this file, if it is
available, or None if it is not. """
if self.fileType == FTPythonCompiled or \
self.fileType == FTCompiledModule:
return None
filename = Filename(self.filename)
filename.setExtension('py')
try:
file = open(filename, 'rU')
except IOError:
return None
return file.read()
def _import_compiled_module(self, fullname):
""" Loads the compiled C/C++ shared object as a Python module,
and returns it. """
print "importing %s" % (fullname)
vfile = vfs.getFile(self.filename, False)
# We can only import a compiled module if it already exists on
# disk. This means if it's a truly virtual file that has no
# on-disk equivalent, we have to write it to a temporary file
# first.
if hasattr(vfile, 'getMount') and \
isinstance(vfile.getMount(), VirtualFileMountSystem):
# It's a real file.
filename = self.filename
else:
# It's a virtual file. Dump it.
filename = Filename.temporary('', self.filename.getBasenameWoExtension(),
'.' + self.filename.getExtension())
filename.setExtension(self.filename.getExtension())
fin = open(vfile, 'rb')
fout = open(filename, 'wb')
data = fin.read(4096)
while data:
fout.write(data)
data = fin.read(4096)
fin.close()
fout.close()
module = imp.load_module(fullname, None, filename.toOsSpecific(),
self.desc)
module.__file__ = self.filename.cStr()
return module
def _read_code(self):
""" Returns the Python compiled code object for this file, if
it is available, or None if it is not. """
if self.fileType == FTPythonCompiled:
# It's a pyc file; just read it directly.
pycVfile = vfs.getFile(self.filename, False)
if pycVfile:
return self._loadPyc(pycVfile)
return None
elif self.fileType == FTCompiledModule:
return None
# It's a .py file (or an __init__.py file; same thing). Read
# the .pyc file if it is available and current; otherwise read
# the .py file and compile it.
pycFilename = Filename(self.filename)
pycFilename.setExtension(pycExtension)
pycVfile = vfs.getFile(pycFilename, False)
t_pyc = None
if pycVfile:
t_pyc = pycVfile.getTimestamp()
code = None
if t_pyc and t_pyc >= self.timestamp:
code = self._loadPyc(pycVfile)
if not code:
source = self._read_source()
filename = Filename(self.filename)
filename.setExtension('py')
code = self._compile(filename, source)
return code
def _loadPyc(self, vfile):
""" Reads and returns the marshal data from a .pyc file. """
code = None
f = open(vfile, 'rb')
if f.read(4) == imp.get_magic():
t = struct.unpack('<I', f.read(4))[0]
if t == self.timestamp:
code = marshal.loads(f.read())
f.close()
return code
def _compile(self, filename, source):
""" Compiles the Python source code to a code object and
attempts to write it to an appropriate .pyc file. """
if source and source[-1] != '\n':
source = source + '\n'
code = __builtin__.compile(source, filename.cStr(), 'exec')
# try to cache the compiled code
pycFilename = Filename(filename)
pycFilename.setExtension(pycExtension)
try:
f = open(pycFilename, 'wb')
except IOError:
pass
else:
f.write('\0\0\0\0')
f.write(struct.pack('<I', self.timestamp))
f.write(marshal.dumps(code))
f.flush()
f.seek(0, 0)
f.write(imp.get_magic())
f.close()
return code
_registered = False
def register():
""" Register the VFSImporter on the path_hooks, if it has not
already been registered, so that future Python import statements
will vector through here (and therefore will take advantage of
Panda's virtual file system). """
global _registered
if not _registered:
_registered = True
sys.path_hooks.insert(0, VFSImporter)