Panda3D
 All Classes Functions Variables Enumerations
cvsCopy.cxx
1 // Filename: cvsCopy.cxx
2 // Created by: drose (31Oct00)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "cvsCopy.h"
16 #include "cvsSourceDirectory.h"
17 
18 #include "pnotify.h"
19 #include <algorithm>
20 
21 ////////////////////////////////////////////////////////////////////
22 // Function: CVSCopy::Constructor
23 // Access: Public
24 // Description:
25 ////////////////////////////////////////////////////////////////////
26 CVSCopy::
27 CVSCopy() {
28  _model_dirname = ".";
29  _key_filename = "Sources.pp";
30  _cvs_binary = "cvs";
31  _user_aborted = false;
32  _model_dir = (CVSSourceDirectory *)NULL;
33  _map_dir = (CVSSourceDirectory *)NULL;
34 
35  clear_runlines();
36  add_runline("[opts] file [file ... ]");
37 
38  add_option
39  ("f", "", 80,
40  "Force the copy to happen without any input from the user. If a file "
41  "with the same name exists anywhere in the source hierarchy, it will "
42  "be overwritten without prompting; if a file does not yet exist, it "
43  "will be created in the directory named by -d or by -m, as appropriate.",
44  &CVSCopy::dispatch_none, &_force);
45 
46  add_option
47  ("i", "", 80,
48  "The opposite of -f, this will prompt the user before each action. "
49  "The default is only to prompt the user when an action is ambiguous "
50  "or unusual.",
51  &CVSCopy::dispatch_none, &_interactive);
52 
53  add_option
54  ("d", "dirname", 80,
55  "Copy model files that are not already present somewhere in the tree "
56  "to the indicated directory. The default is the current directory.",
57  &CVSCopy::dispatch_filename, &_got_model_dirname, &_model_dirname);
58 
59  add_option
60  ("m", "dirname", 80,
61  "Copy texture map files to the indicated directory. The default "
62  "is src/maps from the root directory.",
63  &CVSCopy::dispatch_filename, &_got_map_dirname, &_map_dirname);
64 
65  add_option
66  ("root", "dirname", 80,
67  "Specify the root of the CVS source hierarchy. The default is to "
68  "use the ppremake convention of locating the directory above the -d "
69  "directory that contains a file called Package.pp.",
70  &CVSCopy::dispatch_filename, &_got_root_dirname, &_root_dirname);
71 
72  add_option
73  ("key", "filename", 80,
74  "Specify the name of the file that must exist in each directory for "
75  "it to be considered part of the CVS source hierarchy. The default "
76  "is the ppremake convention, \"Sources.pp\". Other likely candidates "
77  "are \"CVS\" to search a CVS hierarchy, or \".\" to include "
78  "all subdirectories indiscriminately.",
79  &CVSCopy::dispatch_filename, NULL, &_key_filename);
80 
81  add_option
82  ("nc", "", 80,
83  "Do not attempt to add newly-created files to CVS. The default "
84  "is to add them.",
85  &CVSCopy::dispatch_none, &_no_cvs);
86 
87  add_option
88  ("cvs", "cvs_binary", 80,
89  "Specify how to run the cvs program for adding newly-created files. "
90  "The default is simply \"cvs\".",
91  &CVSCopy::dispatch_string, NULL, &_cvs_binary);
92 }
93 
94 ////////////////////////////////////////////////////////////////////
95 // Function: CVSCopy::import
96 // Access: Public
97 // Description: Checks for the given filename somewhere in the
98 // directory hierarchy, and chooses a place to import
99 // it. Copies the file by calling copy_file().
100 //
101 // Extra_data may be NULL or a pointer to some
102 // user-defined structure; CVSCopy simply passes it
103 // unchanged to copy_file(). It presumably gives the
104 // class a hint as to how the file should be copied.
105 // Suggested_dir is the suggested directory in which to
106 // copy the file, if it does not already exist
107 // elsewhere.
108 //
109 // On success, returns the FilePath it was actually
110 // copied to. On failure, returns an invalid FilePath.
111 ////////////////////////////////////////////////////////////////////
113 import(const Filename &source, void *extra_data,
114  CVSSourceDirectory *suggested_dir) {
115  CopiedFiles::const_iterator ci;
116  ci = _copied_files.find(source);
117  if (ci != _copied_files.end()) {
118  // We have already copied this file.
119  return (*ci).second;
120  }
121 
122  if (!source.exists()) {
123  nout << "Source filename " << source << " does not exist!\n";
124  return CVSSourceTree::FilePath();
125  }
126 
127  string basename = filter_filename(source.get_basename());
128 
130  _tree.choose_directory(basename, suggested_dir, _force, _interactive);
131  nassertr(path.is_valid(), path);
132 
133  _copied_files[source] = path;
134  Filename dest = path.get_fullpath();
135 
136  bool new_file = !dest.exists();
137  if (!new_file && verify_file(source, dest, path._dir, extra_data)) {
138  // The file is unchanged.
139  nout << path.get_path() << " is unchanged.\n";
140 
141  } else {
142  // The file has changed.
143  nout << "Copying " << basename << " to " << path.get_path() << "\n";
144 
145  if (!copy_file(source, dest, path._dir, extra_data, new_file)) {
146  if (!continue_after_error()) {
147  return CVSSourceTree::FilePath();
148  }
149  } else {
150  if (new_file) {
151  cvs_add(dest);
152  }
153  }
154  }
155 
156  return path;
157 }
158 
159 ////////////////////////////////////////////////////////////////////
160 // Function: CVSCopy::continue_after_error
161 // Access: Public
162 // Description: Prompts the user (unless -f was specified) if he
163 // wants to continue the copy operation after some error
164 // has occurred. Returns true to continue, false
165 // otherwise.
166 ////////////////////////////////////////////////////////////////////
167 bool CVSCopy::
169  if (_force) {
170  return true;
171  }
172  if (_user_aborted) {
173  return false;
174  }
175 
176  while (true) {
177  string result = prompt("Error occurred during copy! Continue (y/n)? ");
178  nassertr(!result.empty(), false);
179  if (result.size() == 1) {
180  if (tolower(result[0]) == 'y') {
181  return true;
182  } else if (tolower(result[0]) == 'n') {
183  _user_aborted = true;
184  return false;
185  }
186  }
187 
188  nout << "*** Invalid response: " << result << "\n\n";
189  }
190 }
191 
192 
193 ////////////////////////////////////////////////////////////////////
194 // Function: CVSCopy::handle_args
195 // Access: Protected, Virtual
196 // Description: Does something with the additional arguments on the
197 // command line (after all the -options have been
198 // parsed). Returns true if the arguments are good,
199 // false otherwise.
200 ////////////////////////////////////////////////////////////////////
201 bool CVSCopy::
202 handle_args(Args &args) {
203  if (args.empty()) {
204  nout << "You must specify the file(s) to copy from on the command line.\n";
205  return false;
206  }
207 
208  for (Args::const_iterator ai = args.begin();
209  ai != args.end();
210  ++ai) {
211  _source_files.push_back(Filename::from_os_specific(*ai));
212  }
213  return true;
214 }
215 
216 ////////////////////////////////////////////////////////////////////
217 // Function: CVSCopy::post_command_line
218 // Access: Protected, Virtual
219 // Description: This is called after the command line has been
220 // completely processed, and it gives the program a
221 // chance to do some last-minute processing and
222 // validation of the options and arguments. It should
223 // return true if everything is fine, false if there is
224 // an error.
225 ////////////////////////////////////////////////////////////////////
226 bool CVSCopy::
227 post_command_line() {
228  if (!scan_hierarchy()) {
229  return false;
230  }
231 
232  _model_dir = _tree.find_directory(_model_dirname);
233  if (_model_dir == (CVSSourceDirectory *)NULL) {
234  if (_got_model_dirname) {
235  nout << "Warning: model directory " << _model_dirname
236  << " is not within the source hierarchy.\n";
237  }
238  }
239 
240  if (_got_map_dirname) {
241  _map_dir = _tree.find_directory(_map_dirname);
242 
243  if (_map_dir == (CVSSourceDirectory *)NULL) {
244  nout << "Warning: map directory " << _map_dirname
245  << " is not within the source hierarchy.\n";
246  }
247 
248  } else {
249  _map_dir = _tree.find_relpath("src/maps");
250 
251  if (_map_dir == (CVSSourceDirectory *)NULL) {
252  nout << "Warning: no directory " << _tree.get_root_dirname()
253  << "/src/maps.\n";
254  _map_dir = _model_dir;
255  }
256  }
257 
258  return true;
259 }
260 
261 
262 ////////////////////////////////////////////////////////////////////
263 // Function: CVSCopy::verify_file
264 // Access: Protected, Virtual
265 // Description: Verifies that the file is identical and does not need
266 // to be recopied. Returns true if the files are
267 // identical, false if they differ.
268 ////////////////////////////////////////////////////////////////////
269 bool CVSCopy::
270 verify_file(const Filename &, const Filename &,
271  CVSSourceDirectory *, void *) {
272  return false;
273 }
274 
275 ////////////////////////////////////////////////////////////////////
276 // Function: CVSCopy::verify_binary_file
277 // Access: Protected
278 // Description: Verifies that the file is identical and does not need
279 // to be recopied. Returns true if the files are
280 // identical, false if they differ.
281 ////////////////////////////////////////////////////////////////////
282 bool CVSCopy::
283 verify_binary_file(Filename source, Filename dest) {
284  if (source == dest) {
285  return true;
286  }
287 
288  source.set_binary();
289  dest.set_binary();
290 
291  ifstream s, d;
292  if (!source.open_read(s)) {
293  return false;
294  }
295  if (!dest.open_read(d)) {
296  return false;
297  }
298 
299  int cs, cd;
300  cs = s.get();
301  cd = d.get();
302  while (!s.eof() && !s.fail() && !d.eof() && !d.fail()) {
303  if (cs != cd) {
304  return false;
305  }
306  cs = s.get();
307  cd = d.get();
308  }
309 
310  if (s.fail() || d.fail()) {
311  // If we had some read error, call the files different.
312  return false;
313  }
314 
315  // If we haven't reached the end of one of the files yet, that file
316  // is longer than the other one, and the files are therefore
317  // different.
318  if (!s.eof() || !d.eof()) {
319  return false;
320  }
321 
322  // Otherwise, the files are the same.
323  return true;
324 }
325 
326 ////////////////////////////////////////////////////////////////////
327 // Function: CVSCopy::copy_binary_file
328 // Access: Protected
329 // Description: Copies a file without modifying it or scanning it in
330 // any way. This is particularly useful for copying
331 // textures. This is provided as a convenience function
332 // for derived programs because so many model file
333 // formats will also require copying textures or other
334 // black-box files.
335 ////////////////////////////////////////////////////////////////////
336 bool CVSCopy::
337 copy_binary_file(Filename source, Filename dest) {
338  if (source == dest) {
339  return true;
340  }
341 
342  source.set_binary();
343  dest.set_binary();
344 
345  ifstream in;
346  ofstream out;
347  if (!source.open_read(in)) {
348  nout << "Cannot read " << source << "\n";
349  return false;
350  }
351 
352  dest.unlink();
353  if (!dest.open_write(out)) {
354  nout << "Cannot write " << dest << "\n";
355  return false;
356  }
357 
358  int c;
359  c = in.get();
360  while (!in.eof() && !in.fail() && !out.fail()) {
361  out.put(c);
362  c = in.get();
363  }
364 
365  if (!in.eof() && in.fail()) {
366  nout << "Error reading " << source << "\n";
367  return false;
368  }
369  if (out.fail()) {
370  nout << "Error writing " << dest << "\n";
371  return false;
372  }
373 
374  return true;
375 }
376 
377 ////////////////////////////////////////////////////////////////////
378 // Function: CVSCopy::cvs_add
379 // Access: Protected
380 // Description: Invokes CVS to add the indicated filename to the
381 // repository, if the user so requested. Returns true
382 // if successful, false if there is an error.
383 ////////////////////////////////////////////////////////////////////
384 bool CVSCopy::
385 cvs_add(const Filename &filename) {
386  if (_no_cvs) {
387  return true;
388  }
389 
390  if (!CVSSourceTree::temp_chdir(filename.get_dirname())) {
391  nout << "Invalid directory: " << filename.get_dirname() << "\n";
392  return false;
393  }
394 
395  string command = _cvs_binary + " add -kb " +
396  protect_from_shell(filename.get_basename());
397  nout << command << "\n";
398  int result = system(command.c_str());
399 
401 
402  if (result != 0) {
403  nout << "Failure invoking cvs.\n";
404  return false;
405  }
406  return true;
407 }
408 
409 ////////////////////////////////////////////////////////////////////
410 // Function: CVSCopy::protect_from_shell
411 // Access: Protected, Static
412 // Description: Inserts escape characters into the indicated source
413 // string to protect it from the shell, so that it may
414 // be given on the command line. Returns the modified
415 // string.
416 ////////////////////////////////////////////////////////////////////
417 string CVSCopy::
418 protect_from_shell(const string &source) {
419  string result;
420 
421  for (string::const_iterator pi = source.begin(); pi != source.end(); ++pi) {
422  switch (*pi) {
423  case '\\':
424  case ' ':
425  case '\'':
426  case '"':
427  case '(':
428  case ')':
429  case '<':
430  case '>':
431  case '|':
432  case '&':
433  case '!':
434  case '$':
435  case '~':
436  case '*':
437  case '?':
438  case '[':
439  case ']':
440  case ';':
441  result += '\\';
442  // fall through
443 
444  default:
445  result += *pi;
446  }
447  }
448 
449  return result;
450 }
451 
452 ////////////////////////////////////////////////////////////////////
453 // Function: CVSCopy::filter_filename
454 // Access: Protected, Virtual
455 // Description: Given a source filename (including the basename only,
456 // without a dirname), return the appropriate
457 // corresponding filename within the source directory.
458 // This may be used by derived classes to, for instance,
459 // strip a version number from the filename.
460 ////////////////////////////////////////////////////////////////////
461 string CVSCopy::
462 filter_filename(const string &source) {
463  return source;
464 }
465 
466 ////////////////////////////////////////////////////////////////////
467 // Function: CVSCopy::scan_hierarchy
468 // Access: Private
469 // Description: Starts the scan of the source hierarchy. This
470 // identifies all of the files in the source hierarchy
471 // we're to copy these into, so we can guess where
472 // referenced files should be placed. Returns true if
473 // everything is ok, false if there is an error.
474 ////////////////////////////////////////////////////////////////////
475 bool CVSCopy::
476 scan_hierarchy() {
477  if (!_got_root_dirname) {
478  // If we didn't get a root directory name, find the directory
479  // above this one that contains the file "Package.pp".
480  if (!scan_for_root(_model_dirname)) {
481  return false;
482  }
483  }
484 
485  _tree.set_root(_root_dirname);
486  nout << "Root is " << _tree.get_root_fullpath() << "\n";
487 
488  return _tree.scan(_key_filename);
489 }
490 
491 ////////////////////////////////////////////////////////////////////
492 // Function: CVSCopy::scan_for_root
493 // Access: Private
494 // Description: Searches for the root of the source directory by
495 // looking for the parent directory that contains
496 // "Package.pp". Returns true on success, false on
497 // failure.
498 ////////////////////////////////////////////////////////////////////
499 bool CVSCopy::
500 scan_for_root(const string &dirname) {
501  Filename sources = dirname + "/Sources.pp";
502  if (!sources.exists()) {
503  nout << "Couldn't find " << sources << " in source directory.\n";
504  return false;
505  }
506  Filename package = dirname + "/Package.pp";
507  if (package.exists()) {
508  // Here's the root!
509  _root_dirname = dirname;
510  return true;
511  }
512 
513  return scan_for_root(dirname + "/..");
514 }
515 
516 ////////////////////////////////////////////////////////////////////
517 // Function: CVSCopy::prompt
518 // Access: Private
519 // Description: Issues a prompt to the user and waits for a typed
520 // response. Returns the response (which will not be
521 // empty).
522 ////////////////////////////////////////////////////////////////////
523 string CVSCopy::
524 prompt(const string &message) {
525  nout << flush;
526  while (true) {
527  cerr << message << flush;
528  string response;
529  getline(cin, response);
530 
531  // Remove leading and trailing whitespace.
532  size_t p = 0;
533  while (p < response.length() && isspace(response[p])) {
534  p++;
535  }
536 
537  size_t q = response.length();
538  while (q > p && isspace(response[q - 1])) {
539  q--;
540  }
541 
542  if (q > p) {
543  return response.substr(p, q - p);
544  }
545  }
546 }
string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition: filename.I:398
CVSSourceTree::FilePath import(const Filename &source, void *extra_data, CVSSourceDirectory *suggested_dir)
Checks for the given filename somewhere in the directory hierarchy, and chooses a place to import it...
Definition: cvsCopy.cxx:113
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
Definition: filename.cxx:2554
FilePath choose_directory(const string &basename, CVSSourceDirectory *suggested_dir, bool force, bool interactive)
Determines where an externally referenced model file of the indicated name should go...
void set_binary()
Indicates that the filename represents a binary file.
Definition: filename.I:494
void set_root(const Filename &root_path)
Sets the root of the source directory.
Filename get_root_fullpath()
Returns the full path from the root to the top of the source hierarchy.
bool open_read(ifstream &stream) const
Opens the indicated ifstream for reading the file, if possible.
Definition: filename.cxx:2003
string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:424
CVSSourceDirectory * find_directory(const Filename &path)
Returns the source directory that corresponds to the given path, or NULL if there is no such director...
float length() const
Returns the length of the vector, by the Pythagorean theorem.
Definition: lvecBase3.h:765
CVSSourceDirectory * find_relpath(const string &relpath)
Returns the source directory that corresponds to the given relative path from the root...
This represents one particular directory in the hierarchy of source directory files.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
static bool temp_chdir(const Filename &path)
Temporarily changes the current directory to the named path.
Filename get_root_dirname() const
Returns the local directory name of the root of the tree.
bool continue_after_error()
Prompts the user (unless -f was specified) if he wants to continue the copy operation after some erro...
Definition: cvsCopy.cxx:168
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
bool scan(const Filename &key_filename)
Scans the complete source directory starting at the indicated pathname.
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
bool open_write(ofstream &stream, bool truncate=true) const
Opens the indicated ifstream for writing the file, if possible.
Definition: filename.cxx:2045
static void restore_cwd()
Restores the current directory after changing it from temp_chdir().
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:332