Panda3D
Loading...
Searching...
No Matches
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:44
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.
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
void set_binary()
Indicates that the filename represents a binary file.
Definition filename.I:414
bool open_write(std::ofstream &stream, bool truncate=true) const
Opens the indicated ifstream for writing the file, if possible.
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 physical disk, false otherwise.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.