Panda3D
 All Classes Functions Variables Enumerations
softCVS.cxx
00001 // Filename: softCVS.cxx
00002 // Created by:  drose (10Nov00)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "softCVS.h"
00016 
00017 #include "pnotify.h"
00018 #include "multifile.h"
00019 #include "pystub.h"
00020 
00021 #include <algorithm>
00022 
00023 ////////////////////////////////////////////////////////////////////
00024 //     Function: SoftCVS::Constructor
00025 //       Access: Public
00026 //  Description:
00027 ////////////////////////////////////////////////////////////////////
00028 SoftCVS::
00029 SoftCVS() {
00030   _cvs_binary = "cvs";
00031 
00032   set_program_description
00033     ("softcvs is designed to prepare a directory hierarchy "
00034      "representing a SoftImage database for adding to CVS.  "
00035      "First, it eliminates SoftImage's silly filename-based "
00036      "versioning system by renaming versioned filenames higher "
00037      "than 1-0 back to version 1-0.  Then, it rolls up all the "
00038      "files for each scene except the texture images into a Panda "
00039      "multifile, which is added to CVS; the texture images are "
00040      "directly added to CVS where they are.\n\n"
00041 
00042      "The reduction of hundreds of SoftImage files per scene down to one "
00043      "multifile and a handle of texture images should greatly improve "
00044      "the update and commit times of CVS.\n\n"
00045 
00046      "You must run this from within the root of a SoftImage database "
00047      "directory; e.g. the directory that contains SCENES, PICTURES, MODELS, "
00048      "and so on.");
00049 
00050   clear_runlines();
00051   add_runline("[opts]");
00052 
00053   add_option
00054     ("nc", "", 80,
00055      "Do not attempt to add newly-created files to CVS.  The default "
00056      "is to add them.",
00057      &SoftCVS::dispatch_none, &_no_cvs);
00058 
00059   add_option
00060     ("cvs", "cvs_binary", 80,
00061      "Specify how to run the cvs program for adding newly-created files.  "
00062      "The default is simply \"cvs\".",
00063      &SoftCVS::dispatch_string, NULL, &_cvs_binary);
00064 }
00065 
00066 
00067 ////////////////////////////////////////////////////////////////////
00068 //     Function: SoftCVS::run
00069 //       Access: Public
00070 //  Description:
00071 ////////////////////////////////////////////////////////////////////
00072 void SoftCVS::
00073 run() {
00074   // First, check for the scenes directory.  If it doesn't exist, we
00075   // must not be in the root of a soft database.
00076   Filename scenes = "SCENES/.";
00077   if (!scenes.exists()) {
00078     nout << "No SCENES directory found; you are not in the root of a "
00079       "SoftImage database.\n";
00080     exit(1);
00081   }
00082 
00083   // Also, if we're expecting to use CVS, make sure the CVS directory
00084   // exists.
00085   Filename cvs_entries = "CVS/Entries";
00086   if (!_no_cvs && !cvs_entries.exists()) {
00087     nout << "You do not appear to be within a CVS-controlled source "
00088       "directory.\n";
00089     exit(1);
00090   }
00091 
00092   // Scan all the files in the database.
00093   traverse_root();
00094 
00095   // Collapse out the higher-versioned scene files.
00096   collapse_scene_files();
00097 
00098   // Now determine which element files are actually referenced by at
00099   // least one of the scene files.
00100   if (!get_scenes()) {
00101     exit(1);
00102   }
00103 
00104   // Finally, remove all the element files that are no longer
00105   // referenced by any scenes.
00106   remove_unused_elements();
00107 
00108   // Now do all the cvs adding and removing we need.
00109   if (!_no_cvs) {
00110     cvs_add_or_remove("remove", _cvs_remove);
00111     cvs_add_or_remove("add -kb", _cvs_add);
00112   }
00113 }
00114 
00115 ////////////////////////////////////////////////////////////////////
00116 //     Function: SoftCVS::traverse_root
00117 //       Access: Private
00118 //  Description: Reads all of the toplevel directory names,
00119 //               e.g. SCENES, MATERIALS, etc., and traverses them.
00120 ////////////////////////////////////////////////////////////////////
00121 void SoftCVS::
00122 traverse_root() {
00123   Filename root(".");
00124 
00125   // Get the list of subdirectories.
00126   vector_string subdirs;
00127   if (!root.scan_directory(subdirs)) {
00128     nout << "Unable to scan directory.\n";
00129     return;
00130   }
00131 
00132   vector_string::const_iterator di;
00133   for (di = subdirs.begin(); di != subdirs.end(); ++di) {
00134     Filename subdir = (*di);
00135     if (subdir.is_directory() && subdir != "CVS") {
00136       traverse_subdir(subdir);
00137     }
00138   }
00139 }
00140 
00141 ////////////////////////////////////////////////////////////////////
00142 //     Function: SoftCVS::traverse_subdir
00143 //       Access: Private
00144 //  Description: Reads the directory indicated by prefix and
00145 //               identifies all of the SoftImage files stored there.
00146 ////////////////////////////////////////////////////////////////////
00147 void SoftCVS::
00148 traverse_subdir(const Filename &directory) {
00149   // Get the list of files in the directory.
00150   vector_string files;
00151   if (!directory.scan_directory(files)) {
00152     nout << "Unable to scan directory " << directory << "\n";
00153     return;
00154   }
00155 
00156   // We need to know the set of files in this directory that are CVS
00157   // elements.
00158   pset<string> cvs_elements;
00159   bool in_cvs = false;
00160   if (!_no_cvs) {
00161     in_cvs = scan_cvs(directory, cvs_elements);
00162   }
00163 
00164   bool is_scenes = false;
00165   bool keep_all = false;
00166   bool wants_cvs = false;
00167 
00168   // Now make some special-case behavior based on the particular
00169   // SoftImage subdirectory we're in.
00170   string dirname = directory.get_basename();
00171   if (dirname == "SCENES") {
00172     is_scenes = true;
00173 
00174   } else if (dirname == "CAMERAS") {
00175     // We don't want anything in the cameras directory.  These may
00176     // change arbitrarily and have no bearing on the model or
00177     // animation that we will extract, so avoid them altogether.
00178     return;
00179 
00180   } else if (dirname == "PICTURES") {
00181     // In the pictures directory, we must keep everything, since the
00182     // scene files don't explicitly reference these but they're still
00183     // important.  Textures that are no longer used will pile up; we
00184     // leave this as the user's problem.
00185 
00186     // We not only keep the textures, but we also move them into CVS,
00187     // since (again) they're not part of the scene files and thus
00188     // won't get added to the multifiles.  Also, some textures are
00189     // shared between different scenes, and it would be wasteful to
00190     // add them to each scene multifile; furthermore, some scenes are
00191     // used for animation only, and we don't want to modify these
00192     // multifiles when the textures change.
00193 
00194     keep_all = true;
00195     wants_cvs = !_no_cvs;
00196   }
00197 
00198   vector_string::const_iterator fi;
00199   for (fi = files.begin(); fi != files.end(); ++fi) {
00200     const string &filename = (*fi);
00201     if (filename == "CVS") {
00202       // This special filename is not to be considered.
00203 
00204     } else if (filename == "Chapter.rsrc") {
00205       // This special filename should not be considered, except to add
00206       // it to the multifiles.
00207       _global_files.push_back(Filename(directory, filename));
00208 
00209     } else {
00210       SoftFilename soft(directory, filename);
00211 
00212       if (cvs_elements.count(filename) != 0) {
00213         // This file is known to be in CVS.
00214         soft.set_in_cvs(true);
00215       }
00216 
00217       if (keep_all) {
00218         soft.increment_use_count();
00219       }
00220       if (wants_cvs && !in_cvs) {
00221         // Try to CVSify the directory.
00222         cvs_add(directory);
00223         in_cvs = true;
00224       }
00225       soft.set_wants_cvs(wants_cvs);
00226 
00227       if (is_scenes && soft.has_version() && soft.get_extension() == ".dsc") {
00228         _scene_files.push_back(soft);
00229       } else {
00230         _element_files.insert(soft);
00231       }
00232     }
00233   }
00234 }
00235 
00236 ////////////////////////////////////////////////////////////////////
00237 //     Function: SoftCVS::collapse_scene_files
00238 //       Access: Private
00239 //  Description: Walks through the list of scene files found, and
00240 //               renames the higher-versioned ones to version 1-0,
00241 //               removing the intervening versions.
00242 ////////////////////////////////////////////////////////////////////
00243 void SoftCVS::
00244 collapse_scene_files() {
00245   // Get a copy of the scene files vector so we can modify it.  Also
00246   // empty out the _scene_files at the same time so we can fill it up
00247   // again.
00248   SceneFiles versions;
00249   versions.swap(_scene_files);
00250 
00251   // And sort them into order so we can easily compare higher and
00252   // lower versions.
00253   sort(versions.begin(), versions.end());
00254 
00255   SceneFiles::iterator vi;
00256   vi = versions.begin();
00257   while (vi != versions.end()) {
00258     SoftFilename &file = (*vi);
00259 
00260     if (!file.is_1_0()) {
00261       // Here's a file that needs to be renamed.  But first, identify
00262       // all the other versions of the same file.
00263       SceneFiles::iterator start_vi;
00264       start_vi = vi;
00265       while (vi != versions.end() && (*vi).is_same_file(file)) {
00266         ++vi;
00267       }
00268 
00269       rename_file(start_vi, vi);
00270 
00271     } else {
00272       ++vi;
00273     }
00274 
00275     file.make_1_0();
00276     _scene_files.push_back(file);
00277   }
00278 }
00279 
00280 ////////////////////////////////////////////////////////////////////
00281 //     Function: SoftCVS::get_scenes
00282 //       Access: Private
00283 //  Description: Walks through the list of scene files and looks for
00284 //               the set of element files referenced by each one,
00285 //               updating multifile accordingly.
00286 ////////////////////////////////////////////////////////////////////
00287 bool SoftCVS::
00288 get_scenes() {
00289   bool okflag = true;
00290 
00291   // We will be added the multifiles to CVS if they're not already
00292   // added, so we have to know which files are in CVS already.
00293   pset<string> cvs_elements;
00294   if (!_no_cvs) {
00295     scan_cvs(".", cvs_elements);
00296   }
00297 
00298   SceneFiles::const_iterator vi;
00299   for (vi = _scene_files.begin(); vi != _scene_files.end(); ++vi) {
00300     const SoftFilename &sf = (*vi);
00301     Filename file(sf.get_dirname(), sf.get_filename());
00302 
00303     file.set_text();
00304     ifstream in;
00305     if (!file.open_read(in)) {
00306       nout << "Unable to read " << file << "\n";
00307     } else {
00308       nout << "Scanning " << file << "\n";
00309 
00310       Multifile multifile;
00311       Filename multifile_name = sf.get_base() + "mf";
00312 
00313       if (!multifile.open_read_write(multifile_name)) {
00314         nout << "Unable to open " << multifile_name << " for updating.\n";
00315         okflag = false;
00316 
00317       } else {
00318         if (!scan_scene_file(in, multifile)) {
00319           okflag = false;
00320         }
00321 
00322         // Add all the global files to the multifile too.  These
00323         // probably can't take compression (since in SoftImage they're
00324         // just the Chapter.rsrc files, each very tiny).
00325         vector_string::const_iterator gi;
00326         for (gi = _global_files.begin(); gi != _global_files.end(); ++gi) {
00327           if (multifile.update_subfile((*gi), (*gi), 0).empty()) {
00328             nout << "Unable to add " << (*gi) << "\n";
00329             okflag = false;
00330           }
00331         }
00332 
00333         // Also add the scene file itself.
00334         if (multifile.update_subfile(file, file, 6).empty()) {
00335           nout << "Unable to add " << file << "\n";
00336           okflag = false;
00337         }
00338 
00339         bool flushed = false;
00340         if (multifile.needs_repack()) {
00341           flushed = multifile.repack();
00342         } else {
00343           flushed = multifile.flush();
00344         }
00345         if (!flushed) {
00346           nout << "Failed to write " << multifile_name << ".\n";
00347           okflag = false;
00348         } else {
00349           nout << "Wrote " << multifile_name << ".\n";
00350 
00351           if (!_no_cvs && cvs_elements.count(multifile_name) == 0) {
00352             // Add the multifile to CVS.
00353             _cvs_add.push_back(multifile_name);
00354           }
00355         }
00356       }
00357     }
00358   }
00359 
00360   return okflag;
00361 }
00362 
00363 
00364 ////////////////////////////////////////////////////////////////////
00365 //     Function: SoftCVS::remove_unused_elements
00366 //       Access: Private
00367 //  Description: Remove all the element files that weren't referenced
00368 //               by any scene file.  Also plan to cvs add all those
00369 //               that were referenced.
00370 ////////////////////////////////////////////////////////////////////
00371 void SoftCVS::
00372 remove_unused_elements() {
00373   ElementFiles::const_iterator fi;
00374   for (fi = _element_files.begin(); fi != _element_files.end(); ++fi) {
00375     const SoftFilename &sf = (*fi);
00376     Filename file(sf.get_dirname(), sf.get_filename());
00377 
00378     if (sf.get_use_count() == 0) {
00379       nout << file << " is unused.\n";
00380 
00381       if (!file.unlink()) {
00382         nout << "Unable to remove " << file << ".\n";
00383 
00384       } else if (sf.get_in_cvs()) {
00385         _cvs_remove.push_back(file);
00386       }
00387 
00388     } else if (sf.get_wants_cvs() && !sf.get_in_cvs()) {
00389       _cvs_add.push_back(file);
00390     }
00391   }
00392 }
00393 
00394 
00395 ////////////////////////////////////////////////////////////////////
00396 //     Function: SoftCVS::rename_file
00397 //       Access: Private
00398 //  Description: Renames the first file in the indicated list to a
00399 //               version 1-0 filename, superceding all the other files
00400 //               in the list.  Returns true if the file is renamed,
00401 //               false otherwise.
00402 ////////////////////////////////////////////////////////////////////
00403 bool SoftCVS::
00404 rename_file(SoftCVS::SceneFiles::iterator begin,
00405             SoftCVS::SceneFiles::iterator end) {
00406   int length = end - begin;
00407   nassertr(length > 0, false);
00408 
00409   SoftFilename &orig = (*begin);
00410 
00411   string dirname = orig.get_dirname();
00412   string source_filename = orig.get_filename();
00413   string dest_filename = orig.get_1_0_filename();
00414 
00415   if (length > 2) {
00416     nout << source_filename << " supercedes:\n";
00417     SceneFiles::const_iterator p;
00418     for (p = begin + 1; p != end; ++p) {
00419       nout << "  " << (*p).get_filename() << "\n";
00420     }
00421 
00422   } else if (length == 2) {
00423     nout << source_filename << " supercedes "
00424          << (*(begin + 1)).get_filename() << ".\n";
00425 
00426   } else {
00427     nout << source_filename << " renamed.\n";
00428   }
00429 
00430   // Now remove all of the "wrong" files.
00431 
00432   SceneFiles::const_iterator p;
00433   for (p = begin + 1; p != end; ++p) {
00434     Filename file((*p).get_dirname(), (*p).get_filename());
00435     if (!file.unlink()) {
00436       nout << "Unable to remove " << file << ".\n";
00437     }
00438   }
00439 
00440   // And rename the good one.
00441   Filename source(dirname, source_filename);
00442   Filename dest(dirname, dest_filename);
00443 
00444   if (!source.rename_to(dest)) {
00445     nout << "Unable to rename " << source << " to " << dest_filename << ".\n";
00446     exit(1);
00447   }
00448 
00449   return true;
00450 }
00451 
00452 ////////////////////////////////////////////////////////////////////
00453 //     Function: SoftCVS::scan_cvs
00454 //       Access: Private
00455 //  Description: Scans the CVS repository in the indicated directory
00456 //               to determine which files are already versioned
00457 //               elements.  Returns true if the directory is
00458 //               CVS-controlled, false otherwise.
00459 ////////////////////////////////////////////////////////////////////
00460 bool SoftCVS::
00461 scan_cvs(const string &dirname, pset<string> &cvs_elements) {
00462   Filename cvs_entries = dirname + "/CVS/Entries";
00463   if (!cvs_entries.exists()) {
00464     return false;
00465   }
00466 
00467   ifstream in;
00468   cvs_entries.set_text();
00469   if (!cvs_entries.open_read(in)) {
00470     nout << "Unable to read CVS directory.\n";
00471     return true;
00472   }
00473 
00474   string line;
00475   getline(in, line);
00476   while (!in.fail() && !in.eof()) {
00477     if (!line.empty() && line[0] == '/') {
00478       size_t slash = line.find('/', 1);
00479       if (slash != string::npos) {
00480         string filename = line.substr(1, slash - 1);
00481 
00482         if (line.substr(slash + 1, 2) == "-1") {
00483           // If the first number after the slash is -1, the file used
00484           // to be here but was recently cvs removed.  It counts as no
00485           // longer being an element.
00486         } else {
00487           cvs_elements.insert(filename);
00488         }
00489       }
00490     }
00491 
00492     getline(in, line);
00493   }
00494 
00495   return true;
00496 }
00497 
00498 ////////////////////////////////////////////////////////////////////
00499 //     Function: SoftCVS::scan_scene_file
00500 //       Access: Private
00501 //  Description: Reads a scene file, looking for references to element
00502 //               files.  For each reference found, increments the
00503 //               appropriate element file's reference count.
00504 ////////////////////////////////////////////////////////////////////
00505 bool SoftCVS::
00506 scan_scene_file(istream &in, Multifile &multifile) {
00507   bool okflag = true;
00508   
00509   int c = in.get();
00510   while (!in.eof() && !in.fail()) {
00511     // Skip whitespace.
00512     while (isspace(c) && !in.eof() && !in.fail()) {
00513       c = in.get();
00514     }
00515 
00516     // Now begin a word.
00517     string word;
00518     while (!isspace(c) && !in.eof() && !in.fail()) {
00519       word += c;
00520       c = in.get();
00521     }
00522 
00523     if (!word.empty()) {
00524       SoftFilename v("", word);
00525 
00526       // Increment the use count on all matching elements of the multiset.
00527       pair<ElementFiles::iterator, ElementFiles::iterator> range;
00528       range = _element_files.equal_range(v);
00529 
00530       ElementFiles::iterator ei;
00531       for (ei = range.first; ei != range.second; ++ei) {
00532         // We cheat and get a non-const reference to the filename out
00533         // of the set.  We can safely do this because incrementing the
00534         // use count won't change its position in the set.
00535         SoftFilename &sf = (SoftFilename &)(*ei);
00536         sf.increment_use_count();
00537 
00538         Filename file(sf.get_dirname(), sf.get_filename());
00539         if (multifile.update_subfile(file, file, 6).empty()) {
00540           nout << "Unable to add " << file << "\n";
00541           okflag = false;
00542         }
00543       }
00544     }
00545   }
00546 
00547   return okflag;
00548 }
00549 
00550 ////////////////////////////////////////////////////////////////////
00551 //     Function: SoftCVS::cvs_add
00552 //       Access: Private
00553 //  Description: Invokes CVS to add just the named file to the
00554 //               repository.  Returns true on success, false on
00555 //               failure.
00556 ////////////////////////////////////////////////////////////////////
00557 bool SoftCVS::
00558 cvs_add(const string &path) {
00559   string command = _cvs_binary + " add -kb " + path;
00560   nout << command << "\n";
00561   int result = system(command.c_str());
00562 
00563   if (result != 0) {
00564     nout << "Failure invoking cvs.\n";
00565     return false;
00566   }
00567   return true;
00568 }
00569 
00570 ////////////////////////////////////////////////////////////////////
00571 //     Function: SoftCVS::cvs_add_or_remove
00572 //       Access: Private
00573 //  Description: Invokes CVS to add (or remove) all of the files in
00574 //               the indicated vector.  Returns true on success, false
00575 //               on failure.
00576 ////////////////////////////////////////////////////////////////////
00577 bool SoftCVS::
00578 cvs_add_or_remove(const string &cvs_command, const vector_string &paths) {
00579   static const int max_command = 4096;
00580 
00581   if (!paths.empty()) {
00582     string command = _cvs_binary + " " + cvs_command;
00583     vector_string::const_iterator pi;
00584     pi = paths.begin();
00585     while (pi != paths.end()) {
00586       const string &path = (*pi);
00587 
00588       if ((int)command.length() + 1 + (int)path.length() >= max_command) {
00589         // Fire off the command now.
00590         nout << command << "\n";
00591         int result = system(command.c_str());
00592 
00593         if (result != 0) {
00594           nout << "Failure invoking cvs.\n";
00595           return false;
00596         }
00597 
00598         command = _cvs_binary + " " + cvs_command;
00599       }
00600 
00601       command += ' ';
00602       command += path;
00603 
00604       ++pi;
00605     }
00606     nout << command << "\n";
00607     int result = system(command.c_str());
00608 
00609     if (result != 0) {
00610       nout << "Failure invoking cvs.\n";
00611       return false;
00612     }
00613   }
00614   return true;
00615 }
00616 
00617 
00618 int main(int argc, char *argv[]) {
00619   // A call to pystub() to force libpystub.so to be linked in.
00620   pystub();
00621 
00622   SoftCVS prog;
00623   prog.parse_command_line(argc, argv);
00624   prog.run();
00625   return 0;
00626 }
 All Classes Functions Variables Enumerations