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