|
|
@@ -0,0 +1,500 @@
|
|
|
+// Filename: virtualFileSystem.cxx
|
|
|
+// Created by: drose (03Aug02)
|
|
|
+//
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+//
|
|
|
+// PANDA 3D SOFTWARE
|
|
|
+// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
|
|
|
+//
|
|
|
+// All use of this software is subject to the terms of the Panda 3d
|
|
|
+// Software license. You should have received a copy of this license
|
|
|
+// along with this source code; you will also find a current copy of
|
|
|
+// the license at http://www.panda3d.org/license.txt .
|
|
|
+//
|
|
|
+// To contact the maintainers of this program write to
|
|
|
+// [email protected] .
|
|
|
+//
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+
|
|
|
+#include "virtualFileSystem.h"
|
|
|
+#include "virtualFileMount.h"
|
|
|
+#include "virtualFileMountMultifile.h"
|
|
|
+#include "virtualFileMountSystem.h"
|
|
|
+#include "dSearchPath.h"
|
|
|
+#include "dcast.h"
|
|
|
+
|
|
|
+VirtualFileSystem *VirtualFileSystem::_global_ptr = NULL;
|
|
|
+
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::Constructor
|
|
|
+// Access: Published
|
|
|
+// Description:
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+VirtualFileSystem::
|
|
|
+VirtualFileSystem() {
|
|
|
+ _cwd = "/";
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::Destructor
|
|
|
+// Access: Published
|
|
|
+// Description:
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+VirtualFileSystem::
|
|
|
+~VirtualFileSystem() {
|
|
|
+ unmount_all();
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::mount
|
|
|
+// Access: Published
|
|
|
+// Description: Mounts the indicated Multifile at the given mount
|
|
|
+// point. If flags contains MF_owns_pointer, the
|
|
|
+// Multifile will be deleted when it is eventually
|
|
|
+// unmounted.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+bool VirtualFileSystem::
|
|
|
+mount(Multifile *multifile, const string &mount_point, int flags) {
|
|
|
+ VirtualFileMountMultifile *mount =
|
|
|
+ new VirtualFileMountMultifile(this, multifile,
|
|
|
+ normalize_mount_point(mount_point),
|
|
|
+ flags);
|
|
|
+ _mounts.push_back(mount);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::mount
|
|
|
+// Access: Published
|
|
|
+// Description: Mounts the indicated system file or directory at the
|
|
|
+// given mount point. If the named file is a directory,
|
|
|
+// mounts the directory. If the named file is a
|
|
|
+// Multifile, mounts it as a Multifile. Returns true on
|
|
|
+// success, false on failure.
|
|
|
+//
|
|
|
+// A given system directory may be mounted to multiple
|
|
|
+// different mount point, and the same mount point may
|
|
|
+// share multiple system directories. In the case of
|
|
|
+// ambiguities, the most-recently mounted system wins.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+bool VirtualFileSystem::
|
|
|
+mount(const Filename &physical_filename, const string &mount_point,
|
|
|
+ int flags) {
|
|
|
+ if (!physical_filename.exists()) {
|
|
|
+ util_cat.warning()
|
|
|
+ << "Attempt to mount " << physical_filename << ", not found.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (physical_filename.is_directory()) {
|
|
|
+ flags &= ~MF_owns_pointer;
|
|
|
+ VirtualFileMountSystem *mount =
|
|
|
+ new VirtualFileMountSystem(this, physical_filename,
|
|
|
+ normalize_mount_point(mount_point),
|
|
|
+ flags);
|
|
|
+ _mounts.push_back(mount);
|
|
|
+ return true;
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // It's not a directory; it must be a Multifile.
|
|
|
+ Multifile *multifile = new Multifile;
|
|
|
+
|
|
|
+ // For now these are always opened read only. Maybe later we'll
|
|
|
+ // support read-write on Multifiles.
|
|
|
+ flags |= MF_read_only;
|
|
|
+ if (!multifile->open_read(physical_filename)) {
|
|
|
+ delete multifile;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // We want to delete this pointer when we're done.
|
|
|
+ flags |= MF_owns_pointer;
|
|
|
+ return mount(multifile, mount_point, flags);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::unmount
|
|
|
+// Access: Published
|
|
|
+// Description: Unmounts all appearances of the indicated Multifile
|
|
|
+// from the file system. Returns the number of
|
|
|
+// appearances unmounted.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+int VirtualFileSystem::
|
|
|
+unmount(Multifile *multifile) {
|
|
|
+ Mounts::iterator ri, wi;
|
|
|
+ wi = ri = _mounts.begin();
|
|
|
+ while (ri != _mounts.end()) {
|
|
|
+ VirtualFileMount *mount = (*ri);
|
|
|
+ (*wi) = mount;
|
|
|
+
|
|
|
+ if (mount->is_exact_type(VirtualFileMountMultifile::get_class_type())) {
|
|
|
+ VirtualFileMountMultifile *mmount =
|
|
|
+ DCAST(VirtualFileMountMultifile, mount);
|
|
|
+ if (mmount->get_multifile() == multifile) {
|
|
|
+ // Remove this one. Don't increment wi.
|
|
|
+ delete mount;
|
|
|
+ } else {
|
|
|
+ // Don't remove this one.
|
|
|
+ ++wi;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // Don't remove this one.
|
|
|
+ ++wi;
|
|
|
+ }
|
|
|
+ ++ri;
|
|
|
+ }
|
|
|
+
|
|
|
+ int num_removed = _mounts.end() - wi;
|
|
|
+ _mounts.erase(wi, _mounts.end());
|
|
|
+ return num_removed;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::unmount
|
|
|
+// Access: Published
|
|
|
+// Description: Unmounts all appearances of the indicated physical
|
|
|
+// filename (either a directory name or a Multifile
|
|
|
+// name) from the file system. Returns the number of
|
|
|
+// appearances unmounted.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+int VirtualFileSystem::
|
|
|
+unmount(const Filename &physical_filename) {
|
|
|
+ Mounts::iterator ri, wi;
|
|
|
+ wi = ri = _mounts.begin();
|
|
|
+ while (ri != _mounts.end()) {
|
|
|
+ VirtualFileMount *mount = (*ri);
|
|
|
+ (*wi) = mount;
|
|
|
+
|
|
|
+ if (mount->get_physical_filename() == physical_filename) {
|
|
|
+ // Remove this one. Don't increment wi.
|
|
|
+ delete mount;
|
|
|
+ } else {
|
|
|
+ // Don't remove this one.
|
|
|
+ ++wi;
|
|
|
+ }
|
|
|
+ ++ri;
|
|
|
+ }
|
|
|
+
|
|
|
+ int num_removed = _mounts.end() - wi;
|
|
|
+ _mounts.erase(wi, _mounts.end());
|
|
|
+ return num_removed;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::unmount_point
|
|
|
+// Access: Published
|
|
|
+// Description: Unmounts all systems attached to the given mount
|
|
|
+// point from the file system. Returns the number of
|
|
|
+// appearances unmounted.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+int VirtualFileSystem::
|
|
|
+unmount_point(const string &mount_point) {
|
|
|
+ Filename nmp = normalize_mount_point(mount_point);
|
|
|
+ Mounts::iterator ri, wi;
|
|
|
+ wi = ri = _mounts.begin();
|
|
|
+ while (ri != _mounts.end()) {
|
|
|
+ VirtualFileMount *mount = (*ri);
|
|
|
+ (*wi) = mount;
|
|
|
+
|
|
|
+ if (mount->get_mount_point() == nmp) {
|
|
|
+ // Remove this one. Don't increment wi.
|
|
|
+ delete mount;
|
|
|
+ } else {
|
|
|
+ // Don't remove this one.
|
|
|
+ ++wi;
|
|
|
+ }
|
|
|
+ ++ri;
|
|
|
+ }
|
|
|
+
|
|
|
+ int num_removed = _mounts.end() - wi;
|
|
|
+ _mounts.erase(wi, _mounts.end());
|
|
|
+ return num_removed;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::unmount_all
|
|
|
+// Access: Published
|
|
|
+// Description: Unmounts all files from the file system. Returns the
|
|
|
+// number of systems unmounted.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+int VirtualFileSystem::
|
|
|
+unmount_all() {
|
|
|
+ Mounts::iterator mi;
|
|
|
+ for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
|
|
|
+ VirtualFileMount *mount = (*mi);
|
|
|
+ delete mount;
|
|
|
+ }
|
|
|
+
|
|
|
+ int num_removed = _mounts.size();
|
|
|
+ _mounts.clear();
|
|
|
+ return num_removed;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::chdir
|
|
|
+// Access: Published
|
|
|
+// Description: Changes the current directory. This is used to
|
|
|
+// resolve relative pathnames in get_file() and/or
|
|
|
+// find_file(). Returns true if successful, false
|
|
|
+// otherwise.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+bool VirtualFileSystem::
|
|
|
+chdir(const Filename &new_directory) {
|
|
|
+ if (new_directory == "/") {
|
|
|
+ // We can always return to the root.
|
|
|
+ _cwd = new_directory;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ PT(VirtualFile) file = get_file(new_directory);
|
|
|
+ if (file != (VirtualFile *)NULL && file->is_directory()) {
|
|
|
+ _cwd = file->get_filename();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::get_cwd
|
|
|
+// Access: Published
|
|
|
+// Description: Returns the current directory name. See chdir().
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+const Filename &VirtualFileSystem::
|
|
|
+get_cwd() const {
|
|
|
+ return _cwd;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::get_file
|
|
|
+// Access: Published
|
|
|
+// Description: Looks up the file by the indicated name in the file
|
|
|
+// system. Returns a VirtualFile pointer representing
|
|
|
+// the file if it is found, or NULL if it is not.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+PT(VirtualFile) VirtualFileSystem::
|
|
|
+get_file(const Filename &file) const {
|
|
|
+ nassertr(!file.empty(), NULL);
|
|
|
+ Filename pathname(file);
|
|
|
+ if (pathname.is_local()) {
|
|
|
+ pathname = Filename(_cwd, file);
|
|
|
+ }
|
|
|
+ pathname.standardize();
|
|
|
+ string strpath = pathname.get_fullpath().substr(1);
|
|
|
+
|
|
|
+ // Now scan all the mount points, from the back (since later mounts
|
|
|
+ // override more recent ones), until a match is found.
|
|
|
+ PT(VirtualFile) found_file = NULL;
|
|
|
+ VirtualFileComposite *composite_file = NULL;
|
|
|
+
|
|
|
+ Mounts::const_reverse_iterator rmi;
|
|
|
+ for (rmi = _mounts.rbegin(); rmi != _mounts.rend(); ++rmi) {
|
|
|
+ VirtualFileMount *mount = (*rmi);
|
|
|
+ string mount_point = mount->get_mount_point();
|
|
|
+ if (strpath == mount_point) {
|
|
|
+ // Here's an exact match on the mount point. This filename is
|
|
|
+ // the root directory of this mount object.
|
|
|
+ if (found_match(found_file, composite_file, mount, "")) {
|
|
|
+ return found_file;
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if (mount_point.empty()) {
|
|
|
+ // This is the root mount point; all files are in here.
|
|
|
+ if (mount->has_file(strpath)) {
|
|
|
+ // Bingo!
|
|
|
+ if (found_match(found_file, composite_file, mount, strpath)) {
|
|
|
+ return found_file;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if (strpath.length() > mount_point.length() &&
|
|
|
+ strpath.substr(0, mount_point.length()) == mount_point &&
|
|
|
+ strpath[mount_point.length()] == '/') {
|
|
|
+ // This pathname falls within this mount system.
|
|
|
+ Filename local_filename = strpath.substr(mount_point.length() + 1);
|
|
|
+ if (mount->has_file(local_filename)) {
|
|
|
+ // Bingo!
|
|
|
+ if (found_match(found_file, composite_file, mount, local_filename)) {
|
|
|
+ return found_file;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return found_file;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::find_file
|
|
|
+// Access: Published
|
|
|
+// Description: Uses the indicated search path to find the file
|
|
|
+// within the file system. Returns the first occurrence
|
|
|
+// of the file found, or NULL if the file cannot be
|
|
|
+// found.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+PT(VirtualFile) VirtualFileSystem::
|
|
|
+find_file(const Filename &file, const DSearchPath &searchpath) const {
|
|
|
+ if (file.is_local()) {
|
|
|
+ return get_file(file);
|
|
|
+ }
|
|
|
+
|
|
|
+ int num_directories = searchpath.get_num_directories();
|
|
|
+ for (int i = 0; i < num_directories; i++) {
|
|
|
+ Filename match(searchpath.get_directory(i), file);
|
|
|
+ PT(VirtualFile) found_file = get_file(match);
|
|
|
+ if (found_file != (VirtualFile *)NULL) {
|
|
|
+ return found_file;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::write
|
|
|
+// Access: Published
|
|
|
+// Description:
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+void VirtualFileSystem::
|
|
|
+write(ostream &out) const {
|
|
|
+ Mounts::const_iterator mi;
|
|
|
+ for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
|
|
|
+ VirtualFileMount *mount = (*mi);
|
|
|
+ mount->write(out);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::get_global_ptr
|
|
|
+// Access: Published, Static
|
|
|
+// Description: Returns the default global VirtualFileSystem. You
|
|
|
+// may create your own personal VirtualFileSystem
|
|
|
+// objects and use them for whatever you like, but Panda
|
|
|
+// will attempt to load models and stuff from this
|
|
|
+// default object.
|
|
|
+//
|
|
|
+// Initially, the global VirtualFileSystem is set up to
|
|
|
+// mount the OS filesystem to root; i.e. it is
|
|
|
+// equivalent to the OS filesystem. This may be
|
|
|
+// subsequently adjusted by the user.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+VirtualFileSystem *VirtualFileSystem::
|
|
|
+get_global_ptr() {
|
|
|
+ if (_global_ptr == (VirtualFileSystem *)NULL) {
|
|
|
+ _global_ptr = new VirtualFileSystem;
|
|
|
+ _global_ptr->mount("/", "/", 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ return _global_ptr;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::scan_mount_points
|
|
|
+// Access: Public
|
|
|
+// Description: Adds to names a list of all the mount points in use
|
|
|
+// that are one directory below path, if any. That is,
|
|
|
+// these are the external files or directories mounted
|
|
|
+// directly to the indicated path.
|
|
|
+//
|
|
|
+// The names vector is filled with a set of basenames,
|
|
|
+// the basename part of the mount point.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+void VirtualFileSystem::
|
|
|
+scan_mount_points(vector_string &names, const Filename &path) const {
|
|
|
+ nassertv(!path.empty() && !path.is_local());
|
|
|
+ string prefix = path.get_fullpath().substr(1);
|
|
|
+ Mounts::const_iterator mi;
|
|
|
+ for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
|
|
|
+ VirtualFileMount *mount = (*mi);
|
|
|
+
|
|
|
+ string mount_point = mount->get_mount_point();
|
|
|
+ if (prefix.empty()) {
|
|
|
+ // The indicated path is the root. Is the mount point on the
|
|
|
+ // root?
|
|
|
+ if (mount_point.find('/') == string::npos) {
|
|
|
+ // No embedded slashes, so the mount point is only one
|
|
|
+ // directory below the root.
|
|
|
+ names.push_back(mount_point);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (mount_point.substr(0, prefix.length()) == prefix &&
|
|
|
+ mount_point.length() > prefix.length() &&
|
|
|
+ mount_point[prefix.length()] == '/') {
|
|
|
+ // This mount point is below the indicated path. Is it only one
|
|
|
+ // directory below?
|
|
|
+ string basename = mount_point.substr(prefix.length());
|
|
|
+ if (basename.find('/') == string::npos) {
|
|
|
+ // No embedded slashes, so it's only one directory below.
|
|
|
+ names.push_back(basename);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::normalize_mount_point
|
|
|
+// Access: Private
|
|
|
+// Description: Converts the mount point string supplied by the user
|
|
|
+// to standard form (relative to the current directory,
|
|
|
+// with no double slashes, and not terminating with a
|
|
|
+// slash). The initial slash is removed.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+Filename VirtualFileSystem::
|
|
|
+normalize_mount_point(const string &mount_point) const {
|
|
|
+ Filename nmp = mount_point;
|
|
|
+ if (nmp.is_local()) {
|
|
|
+ nmp = Filename(_cwd, mount_point);
|
|
|
+ }
|
|
|
+ nmp.standardize();
|
|
|
+ nassertr(!nmp.empty() && nmp[0] == '/', nmp);
|
|
|
+ return nmp.get_fullpath().substr(1);
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: VirtualFileSystem::found_match
|
|
|
+// Access: Private
|
|
|
+// Description: Evaluates one match found during a get_file()
|
|
|
+// operation. There may be multiple matches for a
|
|
|
+// particular filename due to the ambiguities introduced
|
|
|
+// by allowing multiple mount points, so we may have to
|
|
|
+// keep searching even after the first match is found.
|
|
|
+//
|
|
|
+// Returns true if the search should terminate now, or
|
|
|
+// false if it should keep iterating.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+bool VirtualFileSystem::
|
|
|
+found_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
|
|
|
+ VirtualFileMount *mount, const string &local_filename) const {
|
|
|
+ if (found_file == (VirtualFile *)NULL) {
|
|
|
+ // This was our first match. Save it.
|
|
|
+ found_file = new VirtualFileSimple(mount, local_filename);
|
|
|
+ if (!mount->is_directory(local_filename)) {
|
|
|
+ // If it's not a directory, we're done.
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // This was our second match. The previous match(es) must
|
|
|
+ // have been directories.
|
|
|
+ if (!mount->is_directory(local_filename)) {
|
|
|
+ // However, this one isn't a directory. We're done.
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // At least two directories matched to the same path. We
|
|
|
+ // need a composite directory.
|
|
|
+ if (composite_file == (VirtualFileComposite *)NULL) {
|
|
|
+ composite_file =
|
|
|
+ new VirtualFileComposite((VirtualFileSystem *)this, found_file->get_filename());
|
|
|
+ composite_file->add_component(found_file);
|
|
|
+ found_file = composite_file;
|
|
|
+ }
|
|
|
+ composite_file->add_component(new VirtualFileSimple(mount, local_filename));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Keep going, looking for more directories.
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|