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