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

pandatool/src/cvscopy/cvsCopy.cxx

Go to the documentation of this file.
00001 // Filename: cvsCopy.cxx
00002 // Created by:  drose (31Oct00)
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 "cvsCopy.h"
00020 #include "cvsSourceDirectory.h"
00021 
00022 #include <notify.h>
00023 #include <algorithm>
00024 
00025 ////////////////////////////////////////////////////////////////////
00026 //     Function: CVSCopy::Constructor
00027 //       Access: Public
00028 //  Description:
00029 ////////////////////////////////////////////////////////////////////
00030 CVSCopy::
00031 CVSCopy() {
00032   _model_dirname = ".";
00033   _key_filename = "Sources.pp";
00034   _cvs_binary = "cvs";
00035   _user_aborted = false;
00036   _model_dir = (CVSSourceDirectory *)NULL;
00037   _map_dir = (CVSSourceDirectory *)NULL;
00038 
00039   clear_runlines();
00040   add_runline("[opts] file [file ... ]");
00041 
00042   add_option
00043     ("f", "", 80,
00044      "Force the copy to happen without any input from the user.  If a file "
00045      "with the same name exists anywhere in the source hierarchy, it will "
00046      "be overwritten without prompting; if a file does not yet exist, it "
00047      "will be created in the directory named by -d or by -m, as appropriate.",
00048      &CVSCopy::dispatch_none, &_force);
00049 
00050   add_option
00051     ("i", "", 80,
00052      "The opposite of -f, this will prompt the user before each action.  "
00053      "The default is only to prompt the user when an action is ambiguous "
00054      "or unusual.",
00055      &CVSCopy::dispatch_none, &_interactive);
00056 
00057   add_option
00058     ("d", "dirname", 80,
00059      "Copy model files that are not already present somewhere in the tree "
00060      "to the indicated directory.  The default is the current directory.",
00061      &CVSCopy::dispatch_filename, &_got_model_dirname, &_model_dirname);
00062 
00063   add_option
00064     ("m", "dirname", 80,
00065      "Copy texture map files to the indicated directory.  The default "
00066      "is src/maps from the root directory.",
00067      &CVSCopy::dispatch_filename, &_got_map_dirname, &_map_dirname);
00068 
00069   add_option
00070     ("root", "dirname", 80,
00071      "Specify the root of the CVS source hierarchy.  The default is to "
00072      "use the ppremake convention of locating the directory above the -d "
00073      "directory that contains a file called Package.pp.",
00074      &CVSCopy::dispatch_filename, &_got_root_dirname, &_root_dirname);
00075 
00076   add_option
00077     ("key", "filename", 80,
00078      "Specify the name of the file that must exist in each directory for "
00079      "it to be considered part of the CVS source hierarchy.  The default "
00080      "is the ppremake convention, \"Sources.pp\".  Other likely candidates "
00081      "are \"CVS\" to search a CVS hierarchy, or \".\" to include "
00082      "all subdirectories indiscriminately.",
00083      &CVSCopy::dispatch_filename, NULL, &_key_filename);
00084 
00085   add_option
00086     ("nc", "", 80,
00087      "Do not attempt to add newly-created files to CVS.  The default "
00088      "is to add them.",
00089      &CVSCopy::dispatch_none, &_no_cvs);
00090 
00091   add_option
00092     ("cvs", "cvs_binary", 80,
00093      "Specify how to run the cvs program for adding newly-created files.  "
00094      "The default is simply \"cvs\".",
00095      &CVSCopy::dispatch_string, NULL, &_cvs_binary);
00096 }
00097 
00098 ////////////////////////////////////////////////////////////////////
00099 //     Function: CVSCopy::import
00100 //       Access: Public
00101 //  Description: Checks for the given filename somewhere in the
00102 //               directory hierarchy, and chooses a place to import
00103 //               it.  Copies the file by calling copy_file().
00104 //
00105 //               Extra_data may be NULL or a pointer to some
00106 //               user-defined structure; CVSCopy simply passes it
00107 //               unchanged to copy_file().  It presumably gives the
00108 //               class a hint as to how the file should be copied.
00109 //               Suggested_dir is the suggested directory in which to
00110 //               copy the file, if it does not already exist
00111 //               elsewhere.
00112 //
00113 //               On success, returns the CVSSourceDirectory it was
00114 //               actually copied to.  On failure, returns NULL.
00115 ////////////////////////////////////////////////////////////////////
00116 CVSSourceDirectory *CVSCopy::
00117 import(const Filename &source, void *extra_data,
00118        CVSSourceDirectory *suggested_dir) {
00119   CopiedFiles::const_iterator ci;
00120   ci = _copied_files.find(source);
00121   if (ci != _copied_files.end()) {
00122     // We have already copied this file.
00123     return (*ci).second;
00124   }
00125 
00126   if (!source.exists()) {
00127     nout << "Source filename " << source << " does not exist!\n";
00128     return (CVSSourceDirectory *)NULL;
00129   }
00130 
00131   string basename = filter_filename(source.get_basename());
00132 
00133   CVSSourceDirectory *dir =
00134     _tree.choose_directory(basename, suggested_dir, _force, _interactive);
00135   nassertr(dir != (CVSSourceDirectory *)NULL, dir);
00136 
00137   _copied_files[source] = dir;
00138   Filename dest = dir->get_fullpath() + "/" + basename;
00139 
00140   bool new_file = !dest.exists();
00141   if (!new_file && verify_file(source, dest, dir, extra_data)) {
00142     // The file is unchanged.
00143     nout << dir->get_path() + "/" + basename << " is unchanged.\n";
00144 
00145   } else {
00146     // The file has changed.
00147     nout << "Copying " << basename << " to " << dir->get_path() << "\n";
00148 
00149     if (!copy_file(source, dest, dir, extra_data, new_file)) {
00150       if (!continue_after_error()) {
00151         return (CVSSourceDirectory *)NULL;
00152       }
00153     } else {
00154       if (new_file) {
00155         cvs_add(dest);
00156       }
00157     }
00158   }
00159 
00160   return dir;
00161 }
00162 
00163 ////////////////////////////////////////////////////////////////////
00164 //     Function: CVSCopy::continue_after_error
00165 //       Access: Public
00166 //  Description: Prompts the user (unless -f was specified) if he
00167 //               wants to continue the copy operation after some error
00168 //               has occurred.  Returns true to continue, false
00169 //               otherwise.
00170 ////////////////////////////////////////////////////////////////////
00171 bool CVSCopy::
00172 continue_after_error() {
00173   if (_force) {
00174     return true;
00175   }
00176   if (_user_aborted) {
00177     return false;
00178   }
00179 
00180   while (true) {
00181     string result = prompt("Error occurred during copy!  Continue (y/n)? ");
00182     nassertr(!result.empty(), false);
00183     if (result.size() == 1) {
00184       if (tolower(result[0]) == 'y') {
00185         return true;
00186       } else if (tolower(result[0]) == 'n') {
00187         _user_aborted = true;
00188         return false;
00189       }
00190     }
00191 
00192     nout << "*** Invalid response: " << result << "\n\n";
00193   }
00194 }
00195 
00196 
00197 ////////////////////////////////////////////////////////////////////
00198 //     Function: CVSCopy::handle_args
00199 //       Access: Protected, Virtual
00200 //  Description: Does something with the additional arguments on the
00201 //               command line (after all the -options have been
00202 //               parsed).  Returns true if the arguments are good,
00203 //               false otherwise.
00204 ////////////////////////////////////////////////////////////////////
00205 bool CVSCopy::
00206 handle_args(Args &args) {
00207   if (args.empty()) {
00208     nout << "You must specify the file(s) to copy from on the command line.\n";
00209     return false;
00210   }
00211 
00212   copy(args.begin(), args.end(), back_inserter(_source_files));
00213   return true;
00214 }
00215 
00216 ////////////////////////////////////////////////////////////////////
00217 //     Function: CVSCopy::post_command_line
00218 //       Access: Protected, Virtual
00219 //  Description: This is called after the command line has been
00220 //               completely processed, and it gives the program a
00221 //               chance to do some last-minute processing and
00222 //               validation of the options and arguments.  It should
00223 //               return true if everything is fine, false if there is
00224 //               an error.
00225 ////////////////////////////////////////////////////////////////////
00226 bool CVSCopy::
00227 post_command_line() {
00228   if (!scan_hierarchy()) {
00229     return false;
00230   }
00231 
00232   _model_dir = _tree.find_directory(_model_dirname);
00233   if (_model_dir == (CVSSourceDirectory *)NULL) {
00234     if (_got_model_dirname) {
00235       nout << "Warning: model directory " << _model_dirname
00236            << " is not within the source hierarchy.\n";
00237     }
00238   }
00239 
00240   if (_got_map_dirname) {
00241     _map_dir = _tree.find_directory(_map_dirname);
00242 
00243     if (_map_dir == (CVSSourceDirectory *)NULL) {
00244       nout << "Warning: map directory " << _map_dirname
00245            << " is not within the source hierarchy.\n";
00246     }
00247 
00248   } else {
00249     _map_dir = _tree.find_relpath("src/maps");
00250 
00251     if (_map_dir == (CVSSourceDirectory *)NULL) {
00252       nout << "Warning: no directory " << _tree.get_root_dirname()
00253            << "/src/maps.\n";
00254       _map_dir = _model_dir;
00255     }
00256   }
00257 
00258   return true;
00259 }
00260 
00261 
00262 ////////////////////////////////////////////////////////////////////
00263 //     Function: CVSCopy::verify_file
00264 //       Access: Protected, Virtual
00265 //  Description: Verifies that the file is identical and does not need
00266 //               to be recopied.  Returns true if the files are
00267 //               identical, false if they differ.
00268 ////////////////////////////////////////////////////////////////////
00269 bool CVSCopy::
00270 verify_file(const Filename &, const Filename &,
00271             CVSSourceDirectory *, void *) {
00272   return false;
00273 }
00274 
00275 ////////////////////////////////////////////////////////////////////
00276 //     Function: CVSCopy::verify_binary_file
00277 //       Access: Protected
00278 //  Description: Verifies that the file is identical and does not need
00279 //               to be recopied.  Returns true if the files are
00280 //               identical, false if they differ.
00281 ////////////////////////////////////////////////////////////////////
00282 bool CVSCopy::
00283 verify_binary_file(Filename source, Filename dest) {
00284   source.set_binary();
00285   dest.set_binary();
00286 
00287   ifstream s, d;
00288   if (!source.open_read(s)) {
00289     return false;
00290   }
00291   if (!dest.open_read(d)) {
00292     return false;
00293   }
00294 
00295   int cs, cd;
00296   cs = s.get();
00297   cd = d.get();
00298   while (!s.eof() && !s.fail() && !d.eof() && !d.fail()) {
00299     if (cs != cd) {
00300       return false;
00301     }
00302     cs = s.get();
00303     cd = d.get();
00304   }
00305 
00306   if (s.fail() || d.fail()) {
00307     // If we had some read error, call the files different.
00308     return false;
00309   }
00310 
00311   // If we haven't reached the end of one of the files yet, that file
00312   // is longer than the other one, and the files are therefore
00313   // different.
00314   if (!s.eof() || !d.eof()) {
00315     return false;
00316   }
00317 
00318   // Otherwise, the files are the same.
00319   return true;
00320 }
00321 
00322 ////////////////////////////////////////////////////////////////////
00323 //     Function: CVSCopy::copy_binary_file
00324 //       Access: Protected
00325 //  Description: Copies a file without modifying it or scanning it in
00326 //               any way.  This is particularly useful for copying
00327 //               textures.  This is provided as a convenience function
00328 //               for derived programs because so many model file
00329 //               formats will also require copying textures or other
00330 //               black-box files.
00331 ////////////////////////////////////////////////////////////////////
00332 bool CVSCopy::
00333 copy_binary_file(Filename source, Filename dest) {
00334   source.set_binary();
00335   dest.set_binary();
00336 
00337   ifstream in;
00338   ofstream out;
00339   if (!source.open_read(in)) {
00340     nout << "Cannot read " << source << "\n";
00341     return false;
00342   }
00343 
00344   dest.unlink();
00345   if (!dest.open_write(out)) {
00346     nout << "Cannot write " << dest << "\n";
00347     return false;
00348   }
00349 
00350   int c;
00351   c = in.get();
00352   while (!in.eof() && !in.fail() && !out.fail()) {
00353     out.put(c);
00354     c = in.get();
00355   }
00356 
00357   if (!in.eof() && in.fail()) {
00358     nout << "Error reading " << source << "\n";
00359     return false;
00360   }
00361   if (out.fail()) {
00362     nout << "Error writing " << dest << "\n";
00363     return false;
00364   }
00365 
00366   return true;
00367 }
00368 
00369 ////////////////////////////////////////////////////////////////////
00370 //     Function: CVSCopy::cvs_add
00371 //       Access: Protected
00372 //  Description: Invokes CVS to add the indicated filename to the
00373 //               repository, if the user so requested.  Returns true
00374 //               if successful, false if there is an error.
00375 ////////////////////////////////////////////////////////////////////
00376 bool CVSCopy::
00377 cvs_add(const Filename &filename) {
00378   if (_no_cvs) {
00379     return true;
00380   }
00381 
00382   if (!CVSSourceTree::temp_chdir(filename.get_dirname())) {
00383     nout << "Invalid directory: " << filename.get_dirname() << "\n";
00384     return false;
00385   }
00386 
00387   string command = _cvs_binary + " add -kb " + 
00388     protect_from_shell(filename.get_basename());
00389   nout << command << "\n";
00390   int result = system(command.c_str());
00391 
00392   CVSSourceTree::restore_cwd();
00393 
00394   if (result != 0) {
00395     nout << "Failure invoking cvs.\n";
00396     return false;
00397   }
00398   return true;
00399 }
00400 
00401 ////////////////////////////////////////////////////////////////////
00402 //     Function: CVSCopy::protect_from_shell
00403 //       Access: Protected, Static
00404 //  Description: Inserts escape characters into the indicated source
00405 //               string to protect it from the shell, so that it may
00406 //               be given on the command line.  Returns the modified
00407 //               string.
00408 ////////////////////////////////////////////////////////////////////
00409 string CVSCopy::
00410 protect_from_shell(const string &source) {
00411   string result;
00412 
00413   for (string::const_iterator pi = source.begin(); pi != source.end(); ++pi) {
00414     switch (*pi) {
00415     case '\\':
00416     case ' ':
00417     case '\'':
00418     case '"':
00419     case '(':
00420     case ')':
00421     case '<':
00422     case '>':
00423     case '|':
00424     case '&':
00425     case '!':
00426     case '$':
00427     case '~':
00428     case '*':
00429     case '?':
00430     case '[':
00431     case ']':
00432     case ';':
00433       result += '\\';
00434       // fall through
00435 
00436     default:
00437       result += *pi;
00438     }
00439   }
00440 
00441   return result;
00442 }
00443 
00444 ////////////////////////////////////////////////////////////////////
00445 //     Function: CVSCopy::filter_filename
00446 //       Access: Protected, Virtual
00447 //  Description: Given a source filename (including the basename only,
00448 //               without a dirname), return the appropriate
00449 //               corresponding filename within the source directory.
00450 //               This may be used by derived classes to, for instance,
00451 //               strip a version number from the filename.
00452 ////////////////////////////////////////////////////////////////////
00453 string CVSCopy::
00454 filter_filename(const string &source) {
00455   return source;
00456 }
00457 
00458 ////////////////////////////////////////////////////////////////////
00459 //     Function: CVSCopy::scan_hierarchy
00460 //       Access: Private
00461 //  Description: Starts the scan of the source hierarchy.  This
00462 //               identifies all of the files in the source hierarchy
00463 //               we're to copy these into, so we can guess where
00464 //               referenced files should be placed.  Returns true if
00465 //               everything is ok, false if there is an error.
00466 ////////////////////////////////////////////////////////////////////
00467 bool CVSCopy::
00468 scan_hierarchy() {
00469   if (!_got_root_dirname) {
00470     // If we didn't get a root directory name, find the directory
00471     // above this one that contains the file "Package.pp".
00472     if (!scan_for_root(_model_dirname)) {
00473       return false;
00474     }
00475   }
00476 
00477   _tree.set_root(_root_dirname);
00478   nout << "Root is " << _tree.get_root_fullpath() << "\n";
00479 
00480   return _tree.scan(_key_filename);
00481 }
00482 
00483 ////////////////////////////////////////////////////////////////////
00484 //     Function: CVSCopy::scan_for_root
00485 //       Access: Private
00486 //  Description: Searches for the root of the source directory by
00487 //               looking for the parent directory that contains
00488 //               "Package.pp".  Returns true on success, false on
00489 //               failure.
00490 ////////////////////////////////////////////////////////////////////
00491 bool CVSCopy::
00492 scan_for_root(const string &dirname) {
00493   Filename sources = dirname + "/Sources.pp";
00494   if (!sources.exists()) {
00495     nout << "Couldn't find " << sources << " in source directory.\n";
00496     return false;
00497   }
00498   Filename package = dirname + "/Package.pp";
00499   if (package.exists()) {
00500     // Here's the root!
00501     _root_dirname = dirname;
00502     return true;
00503   }
00504 
00505   return scan_for_root(dirname + "/..");
00506 }
00507 
00508 ////////////////////////////////////////////////////////////////////
00509 //     Function: CVSCopy::prompt
00510 //       Access: Private
00511 //  Description: Issues a prompt to the user and waits for a typed
00512 //               response.  Returns the response (which will not be
00513 //               empty).
00514 ////////////////////////////////////////////////////////////////////
00515 string CVSCopy::
00516 prompt(const string &message) {
00517   nout << flush;
00518   while (true) {
00519     cerr << message << flush;
00520     string response;
00521     getline(cin, response);
00522 
00523     // Remove leading and trailing whitespace.
00524     size_t p = 0;
00525     while (p < response.length() && isspace(response[p])) {
00526       p++;
00527     }
00528 
00529     size_t q = response.length();
00530     while (q > p && isspace(response[q - 1])) {
00531       q--;
00532     }
00533 
00534     if (q > p) {
00535       return response.substr(p, q - p);
00536     }
00537   }
00538 }

Generated on Fri May 2 03:16:58 2003 for Panda-Tool by doxygen1.3