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
20using std::string;
21
22/**
23 *
24 */
25CVSCopy::
26CVSCopy() {
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 */
107import(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";
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()) {
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 */
189bool CVSCopy::
190handle_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 */
210bool CVSCopy::
211post_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 */
250bool CVSCopy::
251verify_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 */
260bool CVSCopy::
261verify_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 */
309bool CVSCopy::
310copy_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 */
354bool CVSCopy::
355cvs_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 */
384string CVSCopy::
385protect_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 */
425string CVSCopy::
426filter_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 */
436bool CVSCopy::
437scan_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 */
457bool CVSCopy::
458scan_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 */
478string CVSCopy::
479prompt(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.