Panda3D
|
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 }