2002-05-15 16:19:20 +00:00

414 lines
13 KiB
C++

// Filename: cvsCopy.cxx
// Created by: drose (31Oct00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////
#include "cvsCopy.h"
#include "cvsSourceDirectory.h"
#include <notify.h>
#include <algorithm>
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CVSCopy::
CVSCopy() {
_model_dirname = ".";
_key_filename = "Sources.pp";
_cvs_binary = "cvs";
_model_dir = (CVSSourceDirectory *)NULL;
_map_dir = (CVSSourceDirectory *)NULL;
clear_runlines();
add_runline("[opts] file [file ... ]");
add_option
("f", "", 80,
"Force the copy to happen without any input from the user. If a file "
"with the same name exists anywhere in the source hierarchy, it will "
"be overwritten without prompting; if a file does not yet exist, it "
"will be created in the directory named by -d or by -m, as appropriate.",
&CVSCopy::dispatch_none, &_force);
add_option
("i", "", 80,
"The opposite of -f, this will prompt the user before each action. "
"The default is only to prompt the user when an action is ambiguous "
"or unusual.",
&CVSCopy::dispatch_none, &_interactive);
add_option
("d", "dirname", 80,
"Copy model files that are not already present somewhere in the tree "
"to the indicated directory. The default is the current directory.",
&CVSCopy::dispatch_filename, &_got_model_dirname, &_model_dirname);
add_option
("m", "dirname", 80,
"Copy texture map files to the indicated directory. The default "
"is src/maps from the root directory.",
&CVSCopy::dispatch_filename, &_got_map_dirname, &_map_dirname);
add_option
("root", "dirname", 80,
"Specify the root of the CVS source hierarchy. The default is to "
"use the ppremake convention of locating the directory above the -d "
"directory that contains a file called Package.pp.",
&CVSCopy::dispatch_filename, &_got_root_dirname, &_root_dirname);
add_option
("key", "filename", 80,
"Specify the name of the file that must exist in each directory for "
"it to be considered part of the CVS source hierarchy. The default "
"is the ppremake convention, \"Sources.pp\". Other likely candidates "
"are \"CVS\" to search a CVS hierarchy, or \".\" to include "
"all subdirectories indiscriminately.",
&CVSCopy::dispatch_filename, NULL, &_key_filename);
add_option
("nc", "", 80,
"Do not attempt to add newly-created files to CVS. The default "
"is to add them.",
&CVSCopy::dispatch_none, &_no_cvs);
add_option
("cvs", "cvs_binary", 80,
"Specify how to run the cvs program for adding newly-created files. "
"The default is simply \"cvs\".",
&CVSCopy::dispatch_string, NULL, &_cvs_binary);
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::import
// Access: Public
// Description: Checks for the given filename somewhere in the
// directory hierarchy, and chooses a place to import
// it. Copies the file by calling copy_file().
//
// Extra_data may be NULL or a pointer to some
// user-defined structure; CVSCopy simply passes it
// unchanged to copy_file(). It presumably gives the
// class a hint as to how the file should be copied.
// Suggested_dir is the suggested directory in which to
// copy the file, if it does not already exist
// elsewhere.
//
// On success, returns the CVSSourceDirectory it was
// actually copied to. On failure, returns NULL.
////////////////////////////////////////////////////////////////////
CVSSourceDirectory *CVSCopy::
import(const Filename &source, void *extra_data,
CVSSourceDirectory *suggested_dir) {
CopiedFiles::const_iterator ci;
ci = _copied_files.find(source);
if (ci != _copied_files.end()) {
// We have already copied this file.
return (*ci).second;
}
if (!source.exists()) {
nout << "Source filename " << source << " does not exist!\n";
return (CVSSourceDirectory *)NULL;
}
string basename = source.get_basename();
CVSSourceDirectory *dir =
_tree.choose_directory(basename, suggested_dir, _force, _interactive);
nassertr(dir != (CVSSourceDirectory *)NULL, dir);
_copied_files[source] = dir;
Filename dest = dir->get_fullpath() + "/" + basename;
bool new_file = !dest.exists();
if (!new_file && verify_file(source, dest, dir, extra_data)) {
// The file is unchanged.
nout << dir->get_path() + "/" + basename << " is unchanged.\n";
} else {
// The file has changed.
nout << "Copying " << basename << " to " << dir->get_path() << "\n";
if (!copy_file(source, dest, dir, extra_data, new_file)) {
return (CVSSourceDirectory *)NULL;
}
if (new_file) {
cvs_add(dest);
}
}
return dir;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::handle_args
// Access: Protected, Virtual
// Description: Does something with the additional arguments on the
// command line (after all the -options have been
// parsed). Returns true if the arguments are good,
// false otherwise.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
handle_args(Args &args) {
if (args.empty()) {
nout << "You must specify the file(s) to copy from on the command line.\n";
return false;
}
copy(args.begin(), args.end(), back_inserter(_source_files));
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::post_command_line
// Access: Protected, Virtual
// Description: This is called after the command line has been
// completely processed, and it gives the program a
// chance to do some last-minute processing and
// validation of the options and arguments. It should
// return true if everything is fine, false if there is
// an error.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
post_command_line() {
if (!scan_hierarchy()) {
return false;
}
_model_dir = _tree.find_directory(_model_dirname);
if (_model_dir == (CVSSourceDirectory *)NULL) {
if (_got_model_dirname) {
nout << "Warning: model directory " << _model_dirname
<< " is not within the source hierarchy.\n";
}
}
if (_got_map_dirname) {
_map_dir = _tree.find_directory(_map_dirname);
if (_map_dir == (CVSSourceDirectory *)NULL) {
nout << "Warning: map directory " << _map_dirname
<< " is not within the source hierarchy.\n";
}
} else {
_map_dir = _tree.find_relpath("src/maps");
if (_map_dir == (CVSSourceDirectory *)NULL) {
nout << "Warning: no directory " << _tree.get_root_dirname()
<< "/src/maps.\n";
_map_dir = _model_dir;
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::verify_file
// Access: Protected, Virtual
// Description: Verifies that the file is identical and does not need
// to be recopied. Returns true if the files are
// identical, false if they differ.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
verify_file(const Filename &, const Filename &,
CVSSourceDirectory *, void *) {
return false;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::verify_binary_file
// Access: Protected
// Description: Verifies that the file is identical and does not need
// to be recopied. Returns true if the files are
// identical, false if they differ.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
verify_binary_file(Filename source, Filename dest) {
source.set_binary();
dest.set_binary();
ifstream s, d;
if (!source.open_read(s)) {
return false;
}
if (!dest.open_read(d)) {
return false;
}
int cs, cd;
cs = s.get();
cd = d.get();
while (!s.eof() && !s.fail() && !d.eof() && !d.fail()) {
if (cs != cd) {
return false;
}
cs = s.get();
cd = d.get();
}
if (s.fail() || d.fail()) {
// If we had some read error, call the files different.
return false;
}
// If we haven't reached the end of one of the files yet, that file
// is longer than the other one, and the files are therefore
// different.
if (!s.eof() || !d.eof()) {
return false;
}
// Otherwise, the files are the same.
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::copy_binary_file
// Access: Protected
// Description: Copies a file without modifying it or scanning it in
// any way. This is particularly useful for copying
// textures. This is provided as a convenience function
// for derived programs because so many model file
// formats will also require copying textures or other
// black-box files.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
copy_binary_file(Filename source, Filename dest) {
source.set_binary();
dest.set_binary();
ifstream in;
ofstream out;
if (!source.open_read(in)) {
nout << "Cannot read " << source << "\n";
return false;
}
dest.unlink();
if (!dest.open_write(out)) {
nout << "Cannot write " << dest << "\n";
return false;
}
int c;
c = in.get();
while (!in.eof() && !in.fail() && !out.fail()) {
out.put(c);
c = in.get();
}
if (!in.eof() && in.fail()) {
nout << "Error reading " << source << "\n";
return false;
}
if (out.fail()) {
nout << "Error writing " << dest << "\n";
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::cvs_add
// Access: Protected
// Description: Invokes CVS to add the indicated filename to the
// repository, if the user so requested. Returns true
// if successful, false if there is an error.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
cvs_add(const Filename &filename) {
if (_no_cvs) {
return true;
}
Filename canon = filename;
if (!CVSSourceTree::temp_chdir(filename.get_dirname())) {
nout << "Invalid directory: " << filename.get_dirname() << "\n";
return false;
}
string command = _cvs_binary + " add -kb " + filename.get_basename();
nout << command << "\n";
int result = system(command.c_str());
CVSSourceTree::restore_cwd();
if (result != 0) {
nout << "Failure invoking cvs.\n";
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::scan_hierarchy
// Access: Private
// Description: Starts the scan of the source hierarchy. This
// identifies all of the files in the source hierarchy
// we're to copy these into, so we can guess where
// referenced files should be placed. Returns true if
// everything is ok, false if there is an error.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
scan_hierarchy() {
if (!_got_root_dirname) {
// If we didn't get a root directory name, find the directory
// above this one that contains the file "Package.pp".
if (!scan_for_root(_model_dirname)) {
return false;
}
}
_tree.set_root(_root_dirname);
nout << "Root is " << _tree.get_root_fullpath() << "\n";
return _tree.scan(_key_filename);
}
////////////////////////////////////////////////////////////////////
// Function: CVSCopy::scan_for_root
// Access: Private
// Description: Searches for the root of the source directory by
// looking for the parent directory that contains
// "Package.pp". Returns true on success, false on
// failure.
////////////////////////////////////////////////////////////////////
bool CVSCopy::
scan_for_root(const string &dirname) {
Filename sources = dirname + "/Sources.pp";
if (!sources.exists()) {
nout << "Couldn't find " << sources << " in source directory.\n";
return false;
}
Filename package = dirname + "/Package.pp";
if (package.exists()) {
// Here's the root!
_root_dirname = dirname;
return true;
}
return scan_for_root(dirname + "/..");
}