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  */
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 }
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
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
This represents one particular directory in the hierarchy of source directory files.
Filename get_path() const
Returns the relative path to this file from the root of the source tree.
bool is_valid() const
Returns true if this FilePath represents a valid file, or false if it represents an error return.
Filename get_fullpath() const
Returns the full path to this file.
Filename get_root_fullpath()
Returns the full path from the root to the top of the source hierarchy.
void set_root(const Filename &root_path)
Sets the root of the source directory.
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.
Filename get_root_dirname() const
Returns the local directory name of the root of the tree.
CVSSourceDirectory * find_relpath(const std::string &relpath)
Returns the source directory that corresponds to the given relative path from the root,...
static bool temp_chdir(const Filename &path)
Temporarily changes the current directory to the named path.
CVSSourceDirectory * find_directory(const Filename &path)
Returns the source directory that corresponds to the given path, or NULL if there is no such director...
static void restore_cwd()
Restores the current directory after changing it from temp_chdir().
bool scan(const Filename &key_filename)
Scans the complete source directory starting at the indicated pathname.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
bool open_read(std::ifstream &stream) const
Opens the indicated ifstream for reading the file, if possible.
Definition: filename.cxx:1863
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
Definition: filename.cxx:2319
void set_binary()
Indicates that the filename represents a binary file.
Definition: filename.I:414
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
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
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1267
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.