mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-03 10:22:45 -04:00
VFSImporter
This commit is contained in:
parent
fdacaa063b
commit
18605b1668
263
direct/src/showbase/VFSImporter.py
Normal file
263
direct/src/showbase/VFSImporter.py
Normal 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)
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user