Panda3D
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_dirname() const
Returns the directory part of the filename.
Definition: filename.I:424
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
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 open_write(ofstream &stream, bool truncate=true) const
Opens the indicated ifstream for writing the file, if possible.
Definition: filename.cxx:2045
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...
Filename get_root_dirname() const
Returns the local directory name of the root of the tree.
string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition: filename.I:398
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.
CVSSourceDirectory * find_directory(const Filename &path)
Returns the source directory that corresponds to the given path, or NULL if there is no such director...
bool open_read(ifstream &stream) const
Opens the indicated ifstream for reading the file, if possible.
Definition: filename.cxx:2003
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.
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
Definition: filename.cxx:2554
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 scan(const Filename &key_filename)
Scans the complete source directory starting at the indicated pathname.
static void restore_cwd()
Restores the current directory after changing it from temp_chdir().
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
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