Main Page   Class Hierarchy   Alphabetical List   Compound List   File List   Compound Members   File Members  

panda/src/express/virtualFileSystem.cxx

Go to the documentation of this file.
00001 // Filename: virtualFileSystem.cxx
00002 // Created by:  drose (03Aug02)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
00008 //
00009 // All use of this software is subject to the terms of the Panda 3d
00010 // Software license.  You should have received a copy of this license
00011 // along with this source code; you will also find a current copy of
00012 // the license at http://www.panda3d.org/license.txt .
00013 //
00014 // To contact the maintainers of this program write to
00015 // panda3d@yahoogroups.com .
00016 //
00017 ////////////////////////////////////////////////////////////////////
00018 
00019 #include "virtualFileSystem.h"
00020 #include "virtualFileMount.h"
00021 #include "virtualFileMountMultifile.h"
00022 #include "virtualFileMountSystem.h"
00023 #include "dSearchPath.h"
00024 #include "dcast.h"
00025 #include "config_express.h"
00026 #include "executionEnvironment.h"
00027 #include "pset.h"
00028 
00029 VirtualFileSystem *VirtualFileSystem::_global_ptr = NULL;
00030 
00031 
00032 ////////////////////////////////////////////////////////////////////
00033 //     Function: VirtualFileSystem::Constructor
00034 //       Access: Published
00035 //  Description: 
00036 ////////////////////////////////////////////////////////////////////
00037 VirtualFileSystem::
00038 VirtualFileSystem() {
00039   _cwd = "/";
00040 }
00041 
00042 ////////////////////////////////////////////////////////////////////
00043 //     Function: VirtualFileSystem::Destructor
00044 //       Access: Published
00045 //  Description: 
00046 ////////////////////////////////////////////////////////////////////
00047 VirtualFileSystem::
00048 ~VirtualFileSystem() {
00049   unmount_all();
00050 }
00051 
00052 ////////////////////////////////////////////////////////////////////
00053 //     Function: VirtualFileSystem::mount
00054 //       Access: Published
00055 //  Description: Mounts the indicated Multifile at the given mount
00056 //               point.  If flags contains MF_owns_pointer, the
00057 //               Multifile will be deleted when it is eventually
00058 //               unmounted.
00059 ////////////////////////////////////////////////////////////////////
00060 bool VirtualFileSystem::
00061 mount(Multifile *multifile, const string &mount_point, int flags) {
00062   VirtualFileMountMultifile *mount = 
00063     new VirtualFileMountMultifile(this, multifile, 
00064                                   normalize_mount_point(mount_point),
00065                                   flags);
00066   _mounts.push_back(mount);
00067   return true;
00068 }
00069 
00070 ////////////////////////////////////////////////////////////////////
00071 //     Function: VirtualFileSystem::mount
00072 //       Access: Published
00073 //  Description: Mounts the indicated system file or directory at the
00074 //               given mount point.  If the named file is a directory,
00075 //               mounts the directory.  If the named file is a
00076 //               Multifile, mounts it as a Multifile.  Returns true on
00077 //               success, false on failure.
00078 //
00079 //               A given system directory may be mounted to multiple
00080 //               different mount point, and the same mount point may
00081 //               share multiple system directories.  In the case of
00082 //               ambiguities, the most-recently mounted system wins.
00083 ////////////////////////////////////////////////////////////////////
00084 bool VirtualFileSystem::
00085 mount(const Filename &physical_filename, const string &mount_point, 
00086       int flags) {
00087   if (!physical_filename.exists()) {
00088     express_cat.warning()
00089       << "Attempt to mount " << physical_filename << ", not found.\n";
00090     return false;
00091   }
00092 
00093   if (physical_filename.is_directory()) {
00094     flags &= ~MF_owns_pointer;
00095     VirtualFileMountSystem *mount =
00096       new VirtualFileMountSystem(this, physical_filename, 
00097                                  normalize_mount_point(mount_point),
00098                                  flags);
00099     _mounts.push_back(mount);
00100     return true;
00101 
00102   } else {
00103     // It's not a directory; it must be a Multifile.
00104     Multifile *multifile = new Multifile;
00105 
00106     // For now these are always opened read only.  Maybe later we'll
00107     // support read-write on Multifiles.
00108     flags |= MF_read_only;
00109     if (!multifile->open_read(physical_filename)) {
00110       delete multifile;
00111       return false;
00112     }
00113 
00114     // We want to delete this pointer when we're done.
00115     flags |= MF_owns_pointer;
00116     return mount(multifile, mount_point, flags);
00117   }
00118 }
00119 
00120 ////////////////////////////////////////////////////////////////////
00121 //     Function: VirtualFileSystem::unmount
00122 //       Access: Published
00123 //  Description: Unmounts all appearances of the indicated Multifile
00124 //               from the file system.  Returns the number of
00125 //               appearances unmounted.
00126 ////////////////////////////////////////////////////////////////////
00127 int VirtualFileSystem::
00128 unmount(Multifile *multifile) {
00129   Mounts::iterator ri, wi;
00130   wi = ri = _mounts.begin();
00131   while (ri != _mounts.end()) {
00132     VirtualFileMount *mount = (*ri);
00133     (*wi) = mount;
00134 
00135     if (mount->is_exact_type(VirtualFileMountMultifile::get_class_type())) {
00136       VirtualFileMountMultifile *mmount = 
00137         DCAST(VirtualFileMountMultifile, mount);
00138       if (mmount->get_multifile() == multifile) {
00139         // Remove this one.  Don't increment wi.
00140         delete mount;
00141       } else {
00142         // Don't remove this one.
00143         ++wi;
00144       }
00145     } else {
00146       // Don't remove this one.
00147       ++wi;
00148     }
00149     ++ri;
00150   }
00151 
00152   int num_removed = _mounts.end() - wi;
00153   _mounts.erase(wi, _mounts.end());
00154   return num_removed;
00155 }
00156 
00157 ////////////////////////////////////////////////////////////////////
00158 //     Function: VirtualFileSystem::unmount
00159 //       Access: Published
00160 //  Description: Unmounts all appearances of the indicated physical
00161 //               filename (either a directory name or a Multifile
00162 //               name) from the file system.  Returns the number of
00163 //               appearances unmounted.
00164 ////////////////////////////////////////////////////////////////////
00165 int VirtualFileSystem::
00166 unmount(const Filename &physical_filename) {
00167   Mounts::iterator ri, wi;
00168   wi = ri = _mounts.begin();
00169   while (ri != _mounts.end()) {
00170     VirtualFileMount *mount = (*ri);
00171     (*wi) = mount;
00172 
00173     if (mount->get_physical_filename() == physical_filename) {
00174       // Remove this one.  Don't increment wi.
00175       delete mount;
00176     } else {
00177       // Don't remove this one.
00178       ++wi;
00179     }
00180     ++ri;
00181   }
00182 
00183   int num_removed = _mounts.end() - wi;
00184   _mounts.erase(wi, _mounts.end());
00185   return num_removed;
00186 }
00187 
00188 ////////////////////////////////////////////////////////////////////
00189 //     Function: VirtualFileSystem::unmount_point
00190 //       Access: Published
00191 //  Description: Unmounts all systems attached to the given mount
00192 //               point from the file system.  Returns the number of
00193 //               appearances unmounted.
00194 ////////////////////////////////////////////////////////////////////
00195 int VirtualFileSystem::
00196 unmount_point(const string &mount_point) {
00197   Filename nmp = normalize_mount_point(mount_point);
00198   Mounts::iterator ri, wi;
00199   wi = ri = _mounts.begin();
00200   while (ri != _mounts.end()) {
00201     VirtualFileMount *mount = (*ri);
00202     (*wi) = mount;
00203 
00204     if (mount->get_mount_point() == nmp) {
00205       // Remove this one.  Don't increment wi.
00206       delete mount;
00207     } else {
00208       // Don't remove this one.
00209       ++wi;
00210     }
00211     ++ri;
00212   }
00213 
00214   int num_removed = _mounts.end() - wi;
00215   _mounts.erase(wi, _mounts.end());
00216   return num_removed;
00217 }
00218 
00219 ////////////////////////////////////////////////////////////////////
00220 //     Function: VirtualFileSystem::unmount_all
00221 //       Access: Published
00222 //  Description: Unmounts all files from the file system.  Returns the
00223 //               number of systems unmounted.
00224 ////////////////////////////////////////////////////////////////////
00225 int VirtualFileSystem::
00226 unmount_all() {
00227   Mounts::iterator mi;
00228   for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
00229     VirtualFileMount *mount = (*mi);
00230     delete mount;
00231   }
00232 
00233   int num_removed = _mounts.size();
00234   _mounts.clear();
00235   return num_removed;
00236 }
00237 
00238 ////////////////////////////////////////////////////////////////////
00239 //     Function: VirtualFileSystem::chdir
00240 //       Access: Published
00241 //  Description: Changes the current directory.  This is used to
00242 //               resolve relative pathnames in get_file() and/or
00243 //               find_file().  Returns true if successful, false
00244 //               otherwise.
00245 //
00246 //               This accepts a string rather than a Filename simply
00247 //               for programmer convenience from the Python prompt.
00248 ////////////////////////////////////////////////////////////////////
00249 bool VirtualFileSystem::
00250 chdir(const string &new_directory) {
00251   if (new_directory == "/") {
00252     // We can always return to the root.
00253     _cwd = new_directory;
00254     return true;
00255   }
00256 
00257   PT(VirtualFile) file = get_file(new_directory);
00258   if (file != (VirtualFile *)NULL && file->is_directory()) {
00259     _cwd = file->get_filename();
00260     return true;
00261   }
00262   return false;
00263 }
00264 
00265 ////////////////////////////////////////////////////////////////////
00266 //     Function: VirtualFileSystem::get_cwd
00267 //       Access: Published
00268 //  Description: Returns the current directory name.  See chdir().
00269 ////////////////////////////////////////////////////////////////////
00270 const Filename &VirtualFileSystem::
00271 get_cwd() const {
00272   return _cwd;
00273 }
00274 
00275 ////////////////////////////////////////////////////////////////////
00276 //     Function: VirtualFileSystem::get_file
00277 //       Access: Published
00278 //  Description: Looks up the file by the indicated name in the file
00279 //               system.  Returns a VirtualFile pointer representing
00280 //               the file if it is found, or NULL if it is not.
00281 ////////////////////////////////////////////////////////////////////
00282 PT(VirtualFile) VirtualFileSystem::
00283 get_file(const Filename &filename) const {
00284   nassertr(!filename.empty(), NULL);
00285   Filename pathname(filename);
00286   if (pathname.is_local()) {
00287     pathname = Filename(_cwd, filename);
00288   }
00289   pathname.standardize();
00290   string strpath = pathname.get_fullpath().substr(1);
00291 
00292   // Now scan all the mount points, from the back (since later mounts
00293   // override more recent ones), until a match is found.
00294   PT(VirtualFile) found_file = NULL;
00295   VirtualFileComposite *composite_file = NULL;
00296 
00297   Mounts::const_reverse_iterator rmi;
00298   for (rmi = _mounts.rbegin(); rmi != _mounts.rend(); ++rmi) {
00299     VirtualFileMount *mount = (*rmi);
00300     string mount_point = mount->get_mount_point();
00301     if (strpath == mount_point) {
00302       // Here's an exact match on the mount point.  This filename is
00303       // the root directory of this mount object.
00304       if (found_match(found_file, composite_file, mount, "")) {
00305         return found_file;
00306       }
00307 
00308     } else if (mount_point.empty()) {
00309       // This is the root mount point; all files are in here.
00310       if (mount->has_file(strpath)) {
00311         // Bingo!
00312         if (found_match(found_file, composite_file, mount, strpath)) {
00313           return found_file;
00314         }
00315       }            
00316 
00317     } else if (strpath.length() > mount_point.length() &&
00318                strpath.substr(0, mount_point.length()) == mount_point &&
00319                strpath[mount_point.length()] == '/') {
00320       // This pathname falls within this mount system.
00321       Filename local_filename = strpath.substr(mount_point.length() + 1);
00322       if (mount->has_file(local_filename)) {
00323         // Bingo!
00324         if (found_match(found_file, composite_file, mount, local_filename)) {
00325           return found_file;
00326         }
00327       }            
00328     }
00329   }
00330   return found_file;
00331 }
00332 
00333 ////////////////////////////////////////////////////////////////////
00334 //     Function: VirtualFileSystem::find_file
00335 //       Access: Published
00336 //  Description: Uses the indicated search path to find the file
00337 //               within the file system.  Returns the first occurrence
00338 //               of the file found, or NULL if the file cannot be
00339 //               found.
00340 ////////////////////////////////////////////////////////////////////
00341 PT(VirtualFile) VirtualFileSystem::
00342 find_file(const Filename &filename, const DSearchPath &searchpath) const {
00343   if (!filename.is_local()) {
00344     return get_file(filename);
00345   }
00346 
00347   int num_directories = searchpath.get_num_directories();
00348   for (int i = 0; i < num_directories; i++) {
00349     Filename match(searchpath.get_directory(i), filename);
00350     if (searchpath.get_directory(i) == "." && 
00351         filename.is_fully_qualified()) {
00352       // A special case for the "." directory: to avoid prefixing
00353       // an endless stream of ./ in front of files, if the
00354       // filename already has a ./ prefixed
00355       // (i.e. is_fully_fully_qualified() is true), we don't
00356       // prefix another one.
00357       match = filename;
00358     }
00359     PT(VirtualFile) found_file = get_file(match);
00360     if (found_file != (VirtualFile *)NULL) {
00361       return found_file;
00362     }
00363   }
00364 
00365   return NULL;
00366 }
00367 
00368 
00369 ////////////////////////////////////////////////////////////////////
00370 //     Function: VirtualFileSystem::resolve_filename
00371 //       Access: Public
00372 //  Description: Searches the given search path for the filename.  If
00373 //               it is found, updates the filename to the full
00374 //               pathname found and returns true; otherwise, returns
00375 //               false.
00376 ////////////////////////////////////////////////////////////////////
00377 bool VirtualFileSystem::
00378 resolve_filename(Filename &filename,
00379                  const DSearchPath &searchpath,
00380                  const string &default_extension) const {
00381   PT(VirtualFile) found;
00382 
00383   if (filename.is_local()) {
00384     found = find_file(filename.get_fullpath(), searchpath);
00385 
00386     if (found.is_null()) {
00387       // We didn't find it with the given extension; can we try the
00388       // default extension?
00389       if (filename.get_extension().empty() && !default_extension.empty()) {
00390         Filename try_ext = filename;
00391         try_ext.set_extension(default_extension);
00392         found = find_file(try_ext.get_fullpath(), searchpath);
00393       }
00394     }
00395 
00396   } else {
00397     if (exists(filename)) {
00398       // The full pathname exists.  Return true.
00399       return true;
00400 
00401     } else {
00402       // The full pathname doesn't exist with the given extension;
00403       // does it exist with the default extension?
00404       if (filename.get_extension().empty() && !default_extension.empty()) {
00405         Filename try_ext = filename;
00406         try_ext.set_extension(default_extension);
00407         found = get_file(try_ext);
00408       }
00409     }
00410   }
00411 
00412   if (!found.is_null()) {
00413     filename = found->get_filename();
00414     return true;
00415   }
00416 
00417   return false;
00418 }
00419 
00420 ////////////////////////////////////////////////////////////////////
00421 //     Function: VirtualFileSystem::find_all_files
00422 //       Access: Public
00423 //  Description: Searches all the directories in the search list for
00424 //               the indicated file, in order.  Fills up the results
00425 //               list with *all* of the matching filenames found, if
00426 //               any.  Returns the number of matches found.
00427 //
00428 //               It is the responsibility of the the caller to clear
00429 //               the results list first; otherwise, the newly-found
00430 //               files will be appended to the list.
00431 ////////////////////////////////////////////////////////////////////
00432 int VirtualFileSystem::
00433 find_all_files(const Filename &filename, const DSearchPath &searchpath,
00434                DSearchPath::Results &results) const {
00435   int num_added = 0;
00436 
00437   if (filename.is_local()) {
00438     int num_directories = searchpath.get_num_directories();
00439     for (int i = 0; i < num_directories; i++) {
00440       Filename match(searchpath.get_directory(i), filename);
00441       if (exists(match)) {
00442         if (searchpath.get_directory(i) == "." && 
00443             filename.is_fully_qualified()) {
00444           // A special case for the "." directory: to avoid prefixing
00445           // an endless stream of ./ in front of files, if the
00446           // filename already has a ./ prefixed
00447           // (i.e. is_fully_fully_qualified() is true), we don't
00448           // prefix another one.
00449           results.add_file(filename);
00450         } else {
00451           results.add_file(match);
00452         }
00453         num_added++;
00454       }
00455     }
00456   }
00457 
00458   return num_added;
00459 }
00460 
00461 ////////////////////////////////////////////////////////////////////
00462 //     Function: VirtualFileSystem::write
00463 //       Access: Published
00464 //  Description: 
00465 ////////////////////////////////////////////////////////////////////
00466 void VirtualFileSystem::
00467 write(ostream &out) const {
00468   Mounts::const_iterator mi;
00469   for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
00470     VirtualFileMount *mount = (*mi);
00471     mount->write(out);
00472   }
00473 }
00474 
00475 
00476 ////////////////////////////////////////////////////////////////////
00477 //     Function: VirtualFileSystem::get_global_ptr
00478 //       Access: Published, Static
00479 //  Description: Returns the default global VirtualFileSystem.  You
00480 //               may create your own personal VirtualFileSystem
00481 //               objects and use them for whatever you like, but Panda
00482 //               will attempt to load models and stuff from this
00483 //               default object.
00484 //
00485 //               Initially, the global VirtualFileSystem is set up to
00486 //               mount the OS filesystem to root; i.e. it is
00487 //               equivalent to the OS filesystem.  This may be
00488 //               subsequently adjusted by the user.
00489 ////////////////////////////////////////////////////////////////////
00490 VirtualFileSystem *VirtualFileSystem::
00491 get_global_ptr() {
00492   if (_global_ptr == (VirtualFileSystem *)NULL) {
00493     _global_ptr = new VirtualFileSystem;
00494     
00495     // Set up the default mounts.  First, there is always the root
00496     // mount.
00497     _global_ptr->mount("/", "/", 0);
00498 
00499     // And our initial cwd comes from the environment.
00500     _global_ptr->chdir(ExecutionEnvironment::get_cwd());
00501 
00502     // Then, we add whatever mounts are listed in the Configrc file.
00503     Config::ConfigTable::Symbol mounts;
00504     config_express.GetAll("vfs-mount", mounts);
00505 
00506     // When we use GetAll(), we might inadvertently read duplicate
00507     // lines.  Filter them out with a set.
00508     pset<string> already_read;
00509 
00510     Config::ConfigTable::Symbol::iterator si;
00511     for (si = mounts.begin(); si != mounts.end(); ++si) {
00512       string mount_desc = (*si).Val();
00513       if (already_read.insert(mount_desc).second) {
00514 
00515         // The vfs-mount syntax is:
00516 
00517         // vfs-mount system-filename mount-point [options]
00518 
00519         // The last two spaces mark the beginning of the mount point,
00520         // and of the options, respectively.  There might be multiple
00521         // spaces in the system filename, which are part of the
00522         // filename.
00523 
00524         // The last space marks the beginning of the mount point.
00525         // Spaces before that are part of the system filename.
00526         size_t space = mount_desc.rfind(' ');
00527         if (space == string::npos) {
00528           express_cat.warning()
00529             << "No space in vfs-mount descriptor: " << mount_desc << "\n";
00530           
00531         } else {
00532           string mount_point = mount_desc.substr(space + 1);
00533           while (space > 0 && isspace(mount_desc[space - 1])) {
00534             space--;
00535           }
00536           mount_desc = mount_desc.substr(0, space);
00537           string options;
00538 
00539           space = mount_desc.rfind(' ');
00540           if (space != string::npos) {
00541             // If there's another space, we have the optional options field.
00542             options = mount_point;
00543             mount_point = mount_desc.substr(space + 1);
00544             while (space > 0 && isspace(mount_desc[space - 1])) {
00545               space--;
00546             }
00547             mount_desc = mount_desc.substr(0, space);
00548           }
00549 
00550           mount_desc = ExecutionEnvironment::expand_string(mount_desc);
00551           Filename physical_filename = Filename::from_os_specific(mount_desc);
00552 
00553           _global_ptr->mount(physical_filename, mount_point, 0);
00554         }
00555       }
00556     }
00557   }
00558 
00559   return _global_ptr;
00560 }
00561 
00562 ////////////////////////////////////////////////////////////////////
00563 //     Function: VirtualFileSystem::scan_mount_points
00564 //       Access: Public
00565 //  Description: Adds to names a list of all the mount points in use
00566 //               that are one directory below path, if any.  That is,
00567 //               these are the external files or directories mounted
00568 //               directly to the indicated path.
00569 //
00570 //               The names vector is filled with a set of basenames,
00571 //               the basename part of the mount point.
00572 ////////////////////////////////////////////////////////////////////
00573 void VirtualFileSystem::
00574 scan_mount_points(vector_string &names, const Filename &path) const {
00575   nassertv(!path.empty() && !path.is_local());
00576   string prefix = path.get_fullpath().substr(1);
00577   Mounts::const_iterator mi;
00578   for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
00579     VirtualFileMount *mount = (*mi);
00580     
00581     string mount_point = mount->get_mount_point();
00582     if (prefix.empty()) {
00583       // The indicated path is the root.  Is the mount point on the
00584       // root?
00585       if (mount_point.find('/') == string::npos) {
00586         // No embedded slashes, so the mount point is only one
00587         // directory below the root.
00588         names.push_back(mount_point);
00589       }
00590     } else {
00591       if (mount_point.substr(0, prefix.length()) == prefix &&
00592           mount_point.length() > prefix.length() &&
00593           mount_point[prefix.length()] == '/') {
00594         // This mount point is below the indicated path.  Is it only one
00595         // directory below?
00596         string basename = mount_point.substr(prefix.length());
00597         if (basename.find('/') == string::npos) {
00598           // No embedded slashes, so it's only one directory below.
00599           names.push_back(basename);
00600         }
00601       }
00602     }
00603   }
00604 }
00605 
00606 ////////////////////////////////////////////////////////////////////
00607 //     Function: VirtualFileSystem::normalize_mount_point
00608 //       Access: Private
00609 //  Description: Converts the mount point string supplied by the user
00610 //               to standard form (relative to the current directory,
00611 //               with no double slashes, and not terminating with a
00612 //               slash).  The initial slash is removed.
00613 ////////////////////////////////////////////////////////////////////
00614 Filename VirtualFileSystem::
00615 normalize_mount_point(const string &mount_point) const {
00616   Filename nmp = mount_point;
00617   if (nmp.is_local()) {
00618     nmp = Filename(_cwd, mount_point);
00619   }
00620   nmp.standardize();
00621   nassertr(!nmp.empty() && nmp[0] == '/', nmp);
00622   return nmp.get_fullpath().substr(1);
00623 }
00624 
00625 ////////////////////////////////////////////////////////////////////
00626 //     Function: VirtualFileSystem::found_match
00627 //       Access: Private
00628 //  Description: Evaluates one match found during a get_file()
00629 //               operation.  There may be multiple matches for a
00630 //               particular filename due to the ambiguities introduced
00631 //               by allowing multiple mount points, so we may have to
00632 //               keep searching even after the first match is found.
00633 //
00634 //               Returns true if the search should terminate now, or
00635 //               false if it should keep iterating.
00636 ////////////////////////////////////////////////////////////////////
00637 bool VirtualFileSystem::
00638 found_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
00639             VirtualFileMount *mount, const string &local_filename) const {
00640   if (found_file == (VirtualFile *)NULL) {
00641     // This was our first match.  Save it.
00642     found_file = new VirtualFileSimple(mount, local_filename);
00643     if (!mount->is_directory(local_filename)) {
00644       // If it's not a directory, we're done.
00645       return true;
00646     }
00647     
00648   } else {
00649     // This was our second match.  The previous match(es) must
00650     // have been directories.
00651     if (!mount->is_directory(local_filename)) {
00652       // However, this one isn't a directory.  We're done.
00653       return true;
00654     }
00655 
00656     // At least two directories matched to the same path.  We
00657     // need a composite directory.
00658     if (composite_file == (VirtualFileComposite *)NULL) {
00659       composite_file =
00660         new VirtualFileComposite((VirtualFileSystem *)this, found_file->get_filename());
00661       composite_file->add_component(found_file);
00662       found_file = composite_file;
00663     }
00664     composite_file->add_component(new VirtualFileSimple(mount, local_filename));
00665   }
00666 
00667   // Keep going, looking for more directories.
00668   return false;
00669 }
00670 

Generated on Fri May 2 00:38:51 2003 for Panda by doxygen1.3