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