Panda3D
 All Classes Functions Variables Enumerations
cvsSourceTree.cxx
1 // Filename: cvsSourceTree.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 "cvsSourceTree.h"
16 #include "cvsSourceDirectory.h"
17 
18 #include "filename.h"
19 #include "executionEnvironment.h"
20 #include "pnotify.h"
21 #include "string_utils.h"
22 
23 #include <algorithm>
24 #include <ctype.h>
25 #include <stdio.h> // for perror
26 #include <errno.h>
27 
28 #ifdef WIN32_VC
29 #include <direct.h> // for chdir
30 #endif
31 
32 bool CVSSourceTree::_got_start_fullpath = false;
33 Filename CVSSourceTree::_start_fullpath;
34 
35 ////////////////////////////////////////////////////////////////////
36 // Function: CVSSourceTree::Constructor
37 // Access: Public
38 // Description:
39 ////////////////////////////////////////////////////////////////////
40 CVSSourceTree::
41 CVSSourceTree() {
42  _root = (CVSSourceDirectory *)NULL;
43  _got_root_fullpath = false;
44 }
45 
46 ////////////////////////////////////////////////////////////////////
47 // Function: CVSSourceTree::Destructor
48 // Access: Public
49 // Description:
50 ////////////////////////////////////////////////////////////////////
51 CVSSourceTree::
52 ~CVSSourceTree() {
53  if (_root != (CVSSourceDirectory *)NULL) {
54  delete _root;
55  }
56 }
57 
58 ////////////////////////////////////////////////////////////////////
59 // Function: CVSSourceTree::set_root
60 // Access: Public
61 // Description: Sets the root of the source directory. This must be
62 // called before scan(), and should not be called more
63 // than once.
64 ////////////////////////////////////////////////////////////////////
65 void CVSSourceTree::
66 set_root(const Filename &root_path) {
67  nassertv(_path.empty());
68  _path = root_path;
69 }
70 
71 ////////////////////////////////////////////////////////////////////
72 // Function: CVSSourceTree::scan
73 // Access: Public
74 // Description: Scans the complete source directory starting at the
75 // indicated pathname. It is an error to call this more
76 // than once. Returns true on success, false if there
77 // is an error.
78 ////////////////////////////////////////////////////////////////////
79 bool CVSSourceTree::
80 scan(const Filename &key_filename) {
81  nassertr(_root == (CVSSourceDirectory *)NULL, false);
82  Filename root_fullpath = get_root_fullpath();
83  _root = new CVSSourceDirectory(this, NULL, root_fullpath.get_basename());
84  return _root->scan(_path, key_filename);
85 }
86 
87 ////////////////////////////////////////////////////////////////////
88 // Function: CVSSourceTree::get_root
89 // Access: Public
90 // Description: Returns the root directory of the hierarchy.
91 ////////////////////////////////////////////////////////////////////
93 get_root() const {
94  return _root;
95 }
96 
97 ////////////////////////////////////////////////////////////////////
98 // Function: CVSSourceTree::find_directory
99 // Access: Public
100 // Description: Returns the source directory that corresponds to the
101 // given path, or NULL if there is no such directory in
102 // the source tree.
103 ////////////////////////////////////////////////////////////////////
105 find_directory(const Filename &path) {
106  string root_fullpath = get_root_fullpath();
107  string fullpath = get_actual_fullpath(path);
108 
109  // path is a subdirectory within the source hierarchy if and only if
110  // root_fullpath is an initial prefix of fullpath.
111  if (root_fullpath.length() > fullpath.length() ||
112  cmp_nocase(fullpath.substr(0, root_fullpath.length()), root_fullpath) != 0) {
113  // Nope!
114  return (CVSSourceDirectory *)NULL;
115  }
116 
117  // The relative name is the part of fullpath not in root_fullpath.
118  Filename relpath = fullpath.substr(root_fullpath.length());
119 
120  return _root->find_relpath(relpath);
121 }
122 
123 ////////////////////////////////////////////////////////////////////
124 // Function: CVSSourceTree::find_relpath
125 // Access: Public
126 // Description: Returns the source directory that corresponds to the
127 // given relative path from the root, or NULL if there
128 // is no match. The relative path may or may not
129 // include the name of the root directory itself.
130 ////////////////////////////////////////////////////////////////////
132 find_relpath(const string &relpath) {
133  CVSSourceDirectory *result = _root->find_relpath(relpath);
134  if (result != (CVSSourceDirectory *)NULL) {
135  return result;
136  }
137 
138  // Check for the root dirname at the front of the path, and remove
139  // it if it's there.
140  size_t slash = relpath.find('/');
141  Filename first = relpath.substr(0, slash);
142  Filename rest;
143  if (slash != string::npos) {
144  rest = relpath.substr(slash + 1);
145  }
146 
147  if (cmp_nocase(first, _root->get_dirname()) == 0) {
148  return _root->find_relpath(rest);
149  }
150 
151  return (CVSSourceDirectory *)NULL;
152 }
153 
154 ////////////////////////////////////////////////////////////////////
155 // Function: CVSSourceTree::find_dirname
156 // Access: Public
157 // Description: Returns the source directory that corresponds to the
158 // given local directory name, or NULL if there
159 // is no match.
160 ////////////////////////////////////////////////////////////////////
162 find_dirname(const string &dirname) {
163  return _root->find_dirname(dirname);
164 }
165 
166 ////////////////////////////////////////////////////////////////////
167 // Function: CVSSourceTree::choose_directory
168 // Access: Public
169 // Description: Determines where an externally referenced model file
170 // of the indicated name should go. It does this by
171 // looking for an existing model file of the same name;
172 // if a matching model is not found, or if multiple
173 // matching files are found, prompts the user for the
174 // directory, or uses suggested_dir.
175 ////////////////////////////////////////////////////////////////////
177 choose_directory(const string &basename, CVSSourceDirectory *suggested_dir,
178  bool force, bool interactive) {
179  static FilePaths empty_paths;
180 
181  Basenames::const_iterator bi;
182  bi = _basenames.find(downcase(basename));
183  if (bi != _basenames.end()) {
184  // The filename already exists somewhere.
185  const FilePaths &paths = (*bi).second;
186 
187  return prompt_user(basename, suggested_dir, paths,
188  force, interactive);
189  }
190 
191  // Now we have to prompt the user for a suitable place to put it.
192  return prompt_user(basename, suggested_dir, empty_paths,
193  force, interactive);
194 }
195 
196 ////////////////////////////////////////////////////////////////////
197 // Function: CVSSourceTree::get_root_fullpath
198 // Access: Public
199 // Description: Returns the full path from the root to the top of
200 // the source hierarchy.
201 ////////////////////////////////////////////////////////////////////
204  nassertr(!_path.empty(), Filename());
205  if (!_got_root_fullpath) {
206  _root_fullpath = get_actual_fullpath(_path);
207  _got_root_fullpath = true;
208  }
209  return _root_fullpath;
210 }
211 
212 ////////////////////////////////////////////////////////////////////
213 // Function: CVSSourceTree::get_root_dirname
214 // Access: Public
215 // Description: Returns the local directory name of the root of the
216 // tree.
217 ////////////////////////////////////////////////////////////////////
220  nassertr(_root != (CVSSourceDirectory *)NULL, Filename());
221  return _root->get_dirname();
222 }
223 
224 ////////////////////////////////////////////////////////////////////
225 // Function: CVSSourceTree::add_file
226 // Access: Public
227 // Description: Adds a new file to the set of known files. This is
228 // normally called from CVSSourceDirectory::scan() and
229 // should not be called directly by the user.
230 ////////////////////////////////////////////////////////////////////
231 void CVSSourceTree::
232 add_file(const string &basename, CVSSourceDirectory *dir) {
233  FilePath file_path(dir, basename);
234  _basenames[downcase(basename)].push_back(file_path);
235 }
236 
237 ////////////////////////////////////////////////////////////////////
238 // Function: CVSSourceTree::temp_chdir
239 // Access: Public, Static
240 // Description: Temporarily changes the current directory to the
241 // named path. Returns true on success, false on
242 // failure. Call restore_cwd() to restore to the
243 // original directory later.
244 ////////////////////////////////////////////////////////////////////
245 bool CVSSourceTree::
246 temp_chdir(const Filename &path) {
247  // We have to call this first to guarantee that we have already
248  // determined our starting directory.
249  get_start_fullpath();
250 
251  string os_path = path.to_os_specific();
252  if (chdir(os_path.c_str()) < 0) {
253  return false;
254  }
255  return true;
256 }
257 
258 ////////////////////////////////////////////////////////////////////
259 // Function: CVSSourceTree::restore_cwd
260 // Access: Public, Static
261 // Description: Restores the current directory after changing it from
262 // temp_chdir().
263 ////////////////////////////////////////////////////////////////////
264 void CVSSourceTree::
266  Filename start_fullpath = get_start_fullpath();
267  string os_path = start_fullpath.to_os_specific();
268 
269  if (chdir(os_path.c_str()) < 0) {
270  // Hey! We can't get back to the directory we started from!
271  perror(os_path.c_str());
272  nout << "Can't continue, aborting.\n";
273  exit(1);
274  }
275 }
276 
277 
278 ////////////////////////////////////////////////////////////////////
279 // Function: CVSSourceTree::prompt_user
280 // Access: Private
281 // Description: Prompts the user, if necessary, to choose a directory
282 // to import the given file into.
283 ////////////////////////////////////////////////////////////////////
284 CVSSourceTree::FilePath CVSSourceTree::
285 prompt_user(const string &basename, CVSSourceDirectory *suggested_dir,
286  const CVSSourceTree::FilePaths &paths,
287  bool force, bool interactive) {
288  if (paths.size() == 1) {
289  // The file already exists in exactly one place.
290  if (!interactive) {
291  return paths[0];
292  }
293  FilePath result = ask_existing(basename, paths[0]);
294  if (result.is_valid()) {
295  return result;
296  }
297 
298  } else if (paths.size() > 1) {
299  // The file already exists in multiple places.
300  if (force && !interactive) {
301  return paths[0];
302  }
303  FilePath result = ask_existing(basename, paths, suggested_dir);
304  if (result.is_valid()) {
305  return result;
306  }
307  }
308 
309  // The file does not already exist, or the user declined to replace
310  // an existing file.
311  if (force && !interactive) {
312  return FilePath(suggested_dir, basename);
313  }
314 
315  // Is the file already in the suggested directory? If not, prompt
316  // the user to put it there.
317  bool found_dir = false;
318  FilePaths::const_iterator pi;
319  for (pi = paths.begin(); pi != paths.end(); ++pi) {
320  if ((*pi)._dir == suggested_dir) {
321  found_dir = true;
322  break;
323  }
324  }
325 
326  if (!found_dir) {
327  FilePath result = ask_new(basename, suggested_dir);
328  if (result.is_valid()) {
329  return result;
330  }
331  }
332 
333  // Ask the user where the damn thing should go.
334  return ask_any(basename, paths);
335 }
336 
337 ////////////////////////////////////////////////////////////////////
338 // Function: CVSSourceTree::ask_existing
339 // Access: Private
340 // Description: Asks the user if he wants to replace an existing
341 // file.
342 ////////////////////////////////////////////////////////////////////
343 CVSSourceTree::FilePath CVSSourceTree::
344 ask_existing(const string &basename, const CVSSourceTree::FilePath &path) {
345  while (true) {
346  nout << basename << " found in tree at "
347  << path.get_path() << ".\n";
348  string result = prompt("Overwrite this file (y/n)? ");
349  nassertr(!result.empty(), FilePath());
350  if (result.size() == 1) {
351  if (tolower(result[0]) == 'y') {
352  return path;
353  } else if (tolower(result[0]) == 'n') {
354  return FilePath();
355  }
356  }
357 
358  nout << "*** Invalid response: " << result << "\n\n";
359  }
360 }
361 
362 ////////////////////////////////////////////////////////////////////
363 // Function: CVSSourceTree::ask_existing
364 // Access: Private
365 // Description: Asks the user which of several existing files he
366 // wants to replace.
367 ////////////////////////////////////////////////////////////////////
368 CVSSourceTree::FilePath CVSSourceTree::
369 ask_existing(const string &basename, const CVSSourceTree::FilePaths &paths,
370  CVSSourceDirectory *suggested_dir) {
371  while (true) {
372  nout << basename << " found in tree at more than one place:\n";
373 
374  bool any_suggested = false;
375  for (int i = 0; i < (int)paths.size(); i++) {
376  nout << " " << (i + 1) << ". "
377  << paths[i].get_path() << "\n";
378  if (paths[i]._dir == suggested_dir) {
379  any_suggested = true;
380  }
381  }
382 
383  int next_option = paths.size() + 1;
384  int suggested_option = -1;
385 
386  if (!any_suggested) {
387  // If it wasn't already in the suggested directory, offer to put
388  // it there.
389  suggested_option = next_option;
390  next_option++;
391  nout << "\n" << suggested_option
392  << ". create "
393  << Filename(suggested_dir->get_path(), basename)
394  << "\n";
395  }
396 
397  int other_option = next_option;
398  nout << other_option << ". Other\n";
399 
400  string result = prompt("Choose an option: ");
401  nassertr(!result.empty(), FilePath());
402  const char *nptr = result.c_str();
403  char *endptr;
404  int option = strtol(nptr, &endptr, 10);
405  if (*endptr == '\0') {
406  if (option >= 1 && option <= (int)paths.size()) {
407  return paths[option - 1];
408 
409  } else if (option == suggested_option) {
410  return FilePath(suggested_dir, basename);
411 
412  } else if (option == other_option) {
413  return FilePath();
414  }
415  }
416 
417  nout << "*** Invalid response: " << result << "\n\n";
418  }
419 }
420 
421 ////////////////////////////////////////////////////////////////////
422 // Function: CVSSourceTree::ask_new
423 // Access: Private
424 // Description: Asks the user if he wants to create a new file.
425 ////////////////////////////////////////////////////////////////////
426 CVSSourceTree::FilePath CVSSourceTree::
427 ask_new(const string &basename, CVSSourceDirectory *dir) {
428  while (true) {
429  nout << basename << " will be created in "
430  << dir->get_path() << ".\n";
431  string result = prompt("Create this file (y/n)? ");
432  nassertr(!result.empty(), FilePath());
433  if (result.size() == 1) {
434  if (tolower(result[0]) == 'y') {
435  return FilePath(dir, basename);
436  } else if (tolower(result[0]) == 'n') {
437  return FilePath();
438  }
439  }
440 
441  nout << "*** Invalid response: " << result << "\n\n";
442  }
443 }
444 
445 ////////////////////////////////////////////////////////////////////
446 // Function: CVSSourceTree::ask_any
447 // Access: Private
448 // Description: Asks the user to type in the name of the directory in
449 // which to store the file.
450 ////////////////////////////////////////////////////////////////////
451 CVSSourceTree::FilePath CVSSourceTree::
452 ask_any(const string &basename,
453  const CVSSourceTree::FilePaths &paths) {
454  while (true) {
455  string result =
456  prompt("Enter the name of the directory to copy " + basename + " to: ");
457  nassertr(!result.empty(), FilePath());
458 
459  // The user might enter a fully-qualified path to the directory,
460  // or a relative path from the root (with or without the root's
461  // dirname), or the dirname of the particular directory.
462  CVSSourceDirectory *dir = find_directory(result);
463  if (dir == (CVSSourceDirectory *)NULL) {
464  dir = find_relpath(result);
465  }
466  if (dir == (CVSSourceDirectory *)NULL) {
467  dir = find_dirname(result);
468  }
469 
470  if (dir != (CVSSourceDirectory *)NULL) {
471  // If the file is already in this directory, we must preserve
472  // its existing case.
473  FilePaths::const_iterator pi;
474  for (pi = paths.begin(); pi != paths.end(); ++pi) {
475  if ((*pi)._dir == dir) {
476  return (*pi);
477  }
478  }
479 
480  // Otherwise, since we're creating a new file, keep the original
481  // case.
482  return FilePath(dir, basename);
483  }
484 
485  nout << "*** Not a valid directory name: " << result << "\n\n";
486  }
487 }
488 
489 ////////////////////////////////////////////////////////////////////
490 // Function: CVSSourceTree::prompt
491 // Access: Private
492 // Description: Issues a prompt to the user and waits for a typed
493 // response. Returns the response (which will not be
494 // empty).
495 ////////////////////////////////////////////////////////////////////
496 string CVSSourceTree::
497 prompt(const string &message) {
498  nout << flush;
499  while (true) {
500  cerr << message << flush;
501  string response;
502  getline(cin, response);
503 
504  // Remove leading and trailing whitespace.
505  size_t p = 0;
506  while (p < response.length() && isspace(response[p])) {
507  p++;
508  }
509 
510  size_t q = response.length();
511  while (q > p && isspace(response[q - 1])) {
512  q--;
513  }
514 
515  if (q > p) {
516  return response.substr(p, q - p);
517  }
518  }
519 }
520 
521 ////////////////////////////////////////////////////////////////////
522 // Function: CVSSourceTree::get_actual_fullpath
523 // Access: Private, Static
524 // Description: Determines the actual full path from the root to the
525 // named directory.
526 ////////////////////////////////////////////////////////////////////
527 Filename CVSSourceTree::
528 get_actual_fullpath(const Filename &path) {
529  Filename canon = path;
530  canon.make_canonical();
531  return canon;
532 }
533 
534 
535 ////////////////////////////////////////////////////////////////////
536 // Function: CVSSourceTree::get_start_fullpath
537 // Access: Private, Static
538 // Description: Returns the full path from the root to the directory
539 // in which the user started the program.
540 ////////////////////////////////////////////////////////////////////
541 Filename CVSSourceTree::
542 get_start_fullpath() {
543  if (!_got_start_fullpath) {
545  _start_fullpath = cwd.to_os_specific();
546  }
547  return _start_fullpath;
548 }
549 
550 
551 ////////////////////////////////////////////////////////////////////
552 // Function: CVSSourceTree::FilePath::Constructor
553 // Access: Public
554 // Description: Creates an invalid FilePath specification.
555 ////////////////////////////////////////////////////////////////////
558  _dir(NULL)
559 {
560 }
561 
562 ////////////////////////////////////////////////////////////////////
563 // Function: CVSSourceTree::FilePath::Constructor
564 // Access: Public
565 // Description: Creates a valid FilePath specification with the
566 // indicated directory and basename.
567 ////////////////////////////////////////////////////////////////////
569 FilePath(CVSSourceDirectory *dir, const string &basename) :
570  _dir(dir),
571  _basename(basename)
572 {
573 }
574 
575 ////////////////////////////////////////////////////////////////////
576 // Function: CVSSourceTree::FilePath::is_valid
577 // Access: Public
578 // Description: Returns true if this FilePath represents a valid
579 // file, or false if it represents an error return.
580 ////////////////////////////////////////////////////////////////////
582 is_valid() const {
583  return (_dir != (CVSSourceDirectory *)NULL);
584 }
585 
586 ////////////////////////////////////////////////////////////////////
587 // Function: CVSSourceTree::FilePath::get_path
588 // Access: Public
589 // Description: Returns the relative path to this file from the root
590 // of the source tree.
591 ////////////////////////////////////////////////////////////////////
593 get_path() const {
594  nassertr(_dir != (CVSSourceDirectory *)NULL, Filename());
595  return Filename(_dir->get_path(), _basename);
596 }
597 
598 ////////////////////////////////////////////////////////////////////
599 // Function: CVSSourceTree::FilePath::get_fullpath
600 // Access: Public
601 // Description: Returns the full path to this file.
602 ////////////////////////////////////////////////////////////////////
604 get_fullpath() const {
605  nassertr(_dir != (CVSSourceDirectory *)NULL, Filename());
606  return Filename(_dir->get_fullpath(), _basename);
607 }
608 
609 ////////////////////////////////////////////////////////////////////
610 // Function: CVSSourceTree::FilePath::get_rel_from
611 // Access: Public
612 // Description: Returns the relative path to this file as seen from
613 // the indicated source directory.
614 ////////////////////////////////////////////////////////////////////
616 get_rel_from(const CVSSourceDirectory *other) const {
617  nassertr(_dir != (CVSSourceDirectory *)NULL, Filename());
618  return Filename(other->get_rel_to(_dir), _basename);
619 }
620 
CVSSourceDirectory * find_dirname(const string &dirname)
Returns the source directory that corresponds to the given local directory name, or NULL if there is ...
Filename get_fullpath() const
Returns the full path to this file.
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_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_relpath(const string &relpath)
Returns the source directory that corresponds to the given relative path from this directory...
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
Definition: filename.cxx:1072
Filename get_rel_from(const CVSSourceDirectory *other) const
Returns the relative path to this file as seen from the indicated source directory.
Filename get_path() const
Returns the relative pathname to this particular directory, as seen from the root of the tree...
Filename get_rel_to(const CVSSourceDirectory *other) const
Returns the relative path to the other directory from this one.
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 is our own Panda specialization on the default STL vector.
Definition: pvector.h:39
float length() const
Returns the length of the vector, by the Pythagorean theorem.
Definition: lvecBase3.h:765
CVSSourceDirectory * find_dirname(const string &dirname)
Returns the source directory that corresponds to the given local directory name, or NULL if there is ...
CVSSourceDirectory * get_root() const
Returns the root directory of the hierarchy.
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
Filename get_path() const
Returns the relative path to this file from the root of the source tree.
void add_file(const string &basename, CVSSourceDirectory *dir)
Adds a new file to the set of known files.
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.
string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1196
bool scan(const Filename &key_filename)
Scans the complete source directory starting at the indicated pathname.
bool is_valid() const
Returns true if this FilePath represents a valid file, or false if it represents an error return...
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
static Filename get_cwd()
Returns the name of the current working directory.
static void restore_cwd()
Restores the current directory after changing it from temp_chdir().
FilePath()
Creates an invalid FilePath specification.
bool scan(const Filename &directory, const string &key_filename)
Recursively scans the contents of the source directory.
string get_dirname() const
Returns the local name of this particular directory.