Panda3D
 All Classes Functions Variables Enumerations
softCVS.cxx
1 // Filename: softCVS.cxx
2 // Created by: drose (10Nov00)
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 "softCVS.h"
16 
17 #include "pnotify.h"
18 #include "multifile.h"
19 #include "pystub.h"
20 
21 #include <algorithm>
22 
23 ////////////////////////////////////////////////////////////////////
24 // Function: SoftCVS::Constructor
25 // Access: Public
26 // Description:
27 ////////////////////////////////////////////////////////////////////
28 SoftCVS::
29 SoftCVS() {
30  _cvs_binary = "cvs";
31 
32  set_program_brief("prepare a SoftImage database directory for adding to CVS");
33  set_program_description
34  ("softcvs is designed to prepare a directory hierarchy "
35  "representing a SoftImage database for adding to CVS. "
36  "First, it eliminates SoftImage's silly filename-based "
37  "versioning system by renaming versioned filenames higher "
38  "than 1-0 back to version 1-0. Then, it rolls up all the "
39  "files for each scene except the texture images into a Panda "
40  "multifile, which is added to CVS; the texture images are "
41  "directly added to CVS where they are.\n\n"
42 
43  "The reduction of hundreds of SoftImage files per scene down to one "
44  "multifile and a handle of texture images should greatly improve "
45  "the update and commit times of CVS.\n\n"
46 
47  "You must run this from within the root of a SoftImage database "
48  "directory; e.g. the directory that contains SCENES, PICTURES, MODELS, "
49  "and so on.");
50 
51  clear_runlines();
52  add_runline("[opts]");
53 
54  add_option
55  ("nc", "", 80,
56  "Do not attempt to add newly-created files to CVS. The default "
57  "is to add them.",
58  &SoftCVS::dispatch_none, &_no_cvs);
59 
60  add_option
61  ("cvs", "cvs_binary", 80,
62  "Specify how to run the cvs program for adding newly-created files. "
63  "The default is simply \"cvs\".",
64  &SoftCVS::dispatch_string, NULL, &_cvs_binary);
65 }
66 
67 
68 ////////////////////////////////////////////////////////////////////
69 // Function: SoftCVS::run
70 // Access: Public
71 // Description:
72 ////////////////////////////////////////////////////////////////////
73 void SoftCVS::
74 run() {
75  // First, check for the scenes directory. If it doesn't exist, we
76  // must not be in the root of a soft database.
77  Filename scenes = "SCENES/.";
78  if (!scenes.exists()) {
79  nout << "No SCENES directory found; you are not in the root of a "
80  "SoftImage database.\n";
81  exit(1);
82  }
83 
84  // Also, if we're expecting to use CVS, make sure the CVS directory
85  // exists.
86  Filename cvs_entries = "CVS/Entries";
87  if (!_no_cvs && !cvs_entries.exists()) {
88  nout << "You do not appear to be within a CVS-controlled source "
89  "directory.\n";
90  exit(1);
91  }
92 
93  // Scan all the files in the database.
94  traverse_root();
95 
96  // Collapse out the higher-versioned scene files.
97  collapse_scene_files();
98 
99  // Now determine which element files are actually referenced by at
100  // least one of the scene files.
101  if (!get_scenes()) {
102  exit(1);
103  }
104 
105  // Finally, remove all the element files that are no longer
106  // referenced by any scenes.
107  remove_unused_elements();
108 
109  // Now do all the cvs adding and removing we need.
110  if (!_no_cvs) {
111  cvs_add_or_remove("remove", _cvs_remove);
112  cvs_add_or_remove("add -kb", _cvs_add);
113  }
114 }
115 
116 ////////////////////////////////////////////////////////////////////
117 // Function: SoftCVS::traverse_root
118 // Access: Private
119 // Description: Reads all of the toplevel directory names,
120 // e.g. SCENES, MATERIALS, etc., and traverses them.
121 ////////////////////////////////////////////////////////////////////
122 void SoftCVS::
123 traverse_root() {
124  Filename root(".");
125 
126  // Get the list of subdirectories.
127  vector_string subdirs;
128  if (!root.scan_directory(subdirs)) {
129  nout << "Unable to scan directory.\n";
130  return;
131  }
132 
133  vector_string::const_iterator di;
134  for (di = subdirs.begin(); di != subdirs.end(); ++di) {
135  Filename subdir = (*di);
136  if (subdir.is_directory() && subdir != "CVS") {
137  traverse_subdir(subdir);
138  }
139  }
140 }
141 
142 ////////////////////////////////////////////////////////////////////
143 // Function: SoftCVS::traverse_subdir
144 // Access: Private
145 // Description: Reads the directory indicated by prefix and
146 // identifies all of the SoftImage files stored there.
147 ////////////////////////////////////////////////////////////////////
148 void SoftCVS::
149 traverse_subdir(const Filename &directory) {
150  // Get the list of files in the directory.
151  vector_string files;
152  if (!directory.scan_directory(files)) {
153  nout << "Unable to scan directory " << directory << "\n";
154  return;
155  }
156 
157  // We need to know the set of files in this directory that are CVS
158  // elements.
159  pset<string> cvs_elements;
160  bool in_cvs = false;
161  if (!_no_cvs) {
162  in_cvs = scan_cvs(directory, cvs_elements);
163  }
164 
165  bool is_scenes = false;
166  bool keep_all = false;
167  bool wants_cvs = false;
168 
169  // Now make some special-case behavior based on the particular
170  // SoftImage subdirectory we're in.
171  string dirname = directory.get_basename();
172  if (dirname == "SCENES") {
173  is_scenes = true;
174 
175  } else if (dirname == "CAMERAS") {
176  // We don't want anything in the cameras directory. These may
177  // change arbitrarily and have no bearing on the model or
178  // animation that we will extract, so avoid them altogether.
179  return;
180 
181  } else if (dirname == "PICTURES") {
182  // In the pictures directory, we must keep everything, since the
183  // scene files don't explicitly reference these but they're still
184  // important. Textures that are no longer used will pile up; we
185  // leave this as the user's problem.
186 
187  // We not only keep the textures, but we also move them into CVS,
188  // since (again) they're not part of the scene files and thus
189  // won't get added to the multifiles. Also, some textures are
190  // shared between different scenes, and it would be wasteful to
191  // add them to each scene multifile; furthermore, some scenes are
192  // used for animation only, and we don't want to modify these
193  // multifiles when the textures change.
194 
195  keep_all = true;
196  wants_cvs = !_no_cvs;
197  }
198 
199  vector_string::const_iterator fi;
200  for (fi = files.begin(); fi != files.end(); ++fi) {
201  const string &filename = (*fi);
202  if (filename == "CVS") {
203  // This special filename is not to be considered.
204 
205  } else if (filename == "Chapter.rsrc") {
206  // This special filename should not be considered, except to add
207  // it to the multifiles.
208  _global_files.push_back(Filename(directory, filename));
209 
210  } else {
211  SoftFilename soft(directory, filename);
212 
213  if (cvs_elements.count(filename) != 0) {
214  // This file is known to be in CVS.
215  soft.set_in_cvs(true);
216  }
217 
218  if (keep_all) {
219  soft.increment_use_count();
220  }
221  if (wants_cvs && !in_cvs) {
222  // Try to CVSify the directory.
223  cvs_add(directory);
224  in_cvs = true;
225  }
226  soft.set_wants_cvs(wants_cvs);
227 
228  if (is_scenes && soft.has_version() && soft.get_extension() == ".dsc") {
229  _scene_files.push_back(soft);
230  } else {
231  _element_files.insert(soft);
232  }
233  }
234  }
235 }
236 
237 ////////////////////////////////////////////////////////////////////
238 // Function: SoftCVS::collapse_scene_files
239 // Access: Private
240 // Description: Walks through the list of scene files found, and
241 // renames the higher-versioned ones to version 1-0,
242 // removing the intervening versions.
243 ////////////////////////////////////////////////////////////////////
244 void SoftCVS::
245 collapse_scene_files() {
246  // Get a copy of the scene files vector so we can modify it. Also
247  // empty out the _scene_files at the same time so we can fill it up
248  // again.
249  SceneFiles versions;
250  versions.swap(_scene_files);
251 
252  // And sort them into order so we can easily compare higher and
253  // lower versions.
254  sort(versions.begin(), versions.end());
255 
256  SceneFiles::iterator vi;
257  vi = versions.begin();
258  while (vi != versions.end()) {
259  SoftFilename &file = (*vi);
260 
261  if (!file.is_1_0()) {
262  // Here's a file that needs to be renamed. But first, identify
263  // all the other versions of the same file.
264  SceneFiles::iterator start_vi;
265  start_vi = vi;
266  while (vi != versions.end() && (*vi).is_same_file(file)) {
267  ++vi;
268  }
269 
270  rename_file(start_vi, vi);
271 
272  } else {
273  ++vi;
274  }
275 
276  file.make_1_0();
277  _scene_files.push_back(file);
278  }
279 }
280 
281 ////////////////////////////////////////////////////////////////////
282 // Function: SoftCVS::get_scenes
283 // Access: Private
284 // Description: Walks through the list of scene files and looks for
285 // the set of element files referenced by each one,
286 // updating multifile accordingly.
287 ////////////////////////////////////////////////////////////////////
288 bool SoftCVS::
289 get_scenes() {
290  bool okflag = true;
291 
292  // We will be added the multifiles to CVS if they're not already
293  // added, so we have to know which files are in CVS already.
294  pset<string> cvs_elements;
295  if (!_no_cvs) {
296  scan_cvs(".", cvs_elements);
297  }
298 
299  SceneFiles::const_iterator vi;
300  for (vi = _scene_files.begin(); vi != _scene_files.end(); ++vi) {
301  const SoftFilename &sf = (*vi);
302  Filename file(sf.get_dirname(), sf.get_filename());
303 
304  file.set_text();
305  ifstream in;
306  if (!file.open_read(in)) {
307  nout << "Unable to read " << file << "\n";
308  } else {
309  nout << "Scanning " << file << "\n";
310 
311  Multifile multifile;
312  Filename multifile_name = sf.get_base() + "mf";
313 
314  if (!multifile.open_read_write(multifile_name)) {
315  nout << "Unable to open " << multifile_name << " for updating.\n";
316  okflag = false;
317 
318  } else {
319  if (!scan_scene_file(in, multifile)) {
320  okflag = false;
321  }
322 
323  // Add all the global files to the multifile too. These
324  // probably can't take compression (since in SoftImage they're
325  // just the Chapter.rsrc files, each very tiny).
326  vector_string::const_iterator gi;
327  for (gi = _global_files.begin(); gi != _global_files.end(); ++gi) {
328  if (multifile.update_subfile((*gi), (*gi), 0).empty()) {
329  nout << "Unable to add " << (*gi) << "\n";
330  okflag = false;
331  }
332  }
333 
334  // Also add the scene file itself.
335  if (multifile.update_subfile(file, file, 6).empty()) {
336  nout << "Unable to add " << file << "\n";
337  okflag = false;
338  }
339 
340  bool flushed = false;
341  if (multifile.needs_repack()) {
342  flushed = multifile.repack();
343  } else {
344  flushed = multifile.flush();
345  }
346  if (!flushed) {
347  nout << "Failed to write " << multifile_name << ".\n";
348  okflag = false;
349  } else {
350  nout << "Wrote " << multifile_name << ".\n";
351 
352  if (!_no_cvs && cvs_elements.count(multifile_name) == 0) {
353  // Add the multifile to CVS.
354  _cvs_add.push_back(multifile_name);
355  }
356  }
357  }
358  }
359  }
360 
361  return okflag;
362 }
363 
364 
365 ////////////////////////////////////////////////////////////////////
366 // Function: SoftCVS::remove_unused_elements
367 // Access: Private
368 // Description: Remove all the element files that weren't referenced
369 // by any scene file. Also plan to cvs add all those
370 // that were referenced.
371 ////////////////////////////////////////////////////////////////////
372 void SoftCVS::
373 remove_unused_elements() {
374  ElementFiles::const_iterator fi;
375  for (fi = _element_files.begin(); fi != _element_files.end(); ++fi) {
376  const SoftFilename &sf = (*fi);
377  Filename file(sf.get_dirname(), sf.get_filename());
378 
379  if (sf.get_use_count() == 0) {
380  nout << file << " is unused.\n";
381 
382  if (!file.unlink()) {
383  nout << "Unable to remove " << file << ".\n";
384 
385  } else if (sf.get_in_cvs()) {
386  _cvs_remove.push_back(file);
387  }
388 
389  } else if (sf.get_wants_cvs() && !sf.get_in_cvs()) {
390  _cvs_add.push_back(file);
391  }
392  }
393 }
394 
395 
396 ////////////////////////////////////////////////////////////////////
397 // Function: SoftCVS::rename_file
398 // Access: Private
399 // Description: Renames the first file in the indicated list to a
400 // version 1-0 filename, superceding all the other files
401 // in the list. Returns true if the file is renamed,
402 // false otherwise.
403 ////////////////////////////////////////////////////////////////////
404 bool SoftCVS::
405 rename_file(SoftCVS::SceneFiles::iterator begin,
406  SoftCVS::SceneFiles::iterator end) {
407  int length = end - begin;
408  nassertr(length > 0, false);
409 
410  SoftFilename &orig = (*begin);
411 
412  string dirname = orig.get_dirname();
413  string source_filename = orig.get_filename();
414  string dest_filename = orig.get_1_0_filename();
415 
416  if (length > 2) {
417  nout << source_filename << " supercedes:\n";
418  SceneFiles::const_iterator p;
419  for (p = begin + 1; p != end; ++p) {
420  nout << " " << (*p).get_filename() << "\n";
421  }
422 
423  } else if (length == 2) {
424  nout << source_filename << " supercedes "
425  << (*(begin + 1)).get_filename() << ".\n";
426 
427  } else {
428  nout << source_filename << " renamed.\n";
429  }
430 
431  // Now remove all of the "wrong" files.
432 
433  SceneFiles::const_iterator p;
434  for (p = begin + 1; p != end; ++p) {
435  Filename file((*p).get_dirname(), (*p).get_filename());
436  if (!file.unlink()) {
437  nout << "Unable to remove " << file << ".\n";
438  }
439  }
440 
441  // And rename the good one.
442  Filename source(dirname, source_filename);
443  Filename dest(dirname, dest_filename);
444 
445  if (!source.rename_to(dest)) {
446  nout << "Unable to rename " << source << " to " << dest_filename << ".\n";
447  exit(1);
448  }
449 
450  return true;
451 }
452 
453 ////////////////////////////////////////////////////////////////////
454 // Function: SoftCVS::scan_cvs
455 // Access: Private
456 // Description: Scans the CVS repository in the indicated directory
457 // to determine which files are already versioned
458 // elements. Returns true if the directory is
459 // CVS-controlled, false otherwise.
460 ////////////////////////////////////////////////////////////////////
461 bool SoftCVS::
462 scan_cvs(const string &dirname, pset<string> &cvs_elements) {
463  Filename cvs_entries = dirname + "/CVS/Entries";
464  if (!cvs_entries.exists()) {
465  return false;
466  }
467 
468  ifstream in;
469  cvs_entries.set_text();
470  if (!cvs_entries.open_read(in)) {
471  nout << "Unable to read CVS directory.\n";
472  return true;
473  }
474 
475  string line;
476  getline(in, line);
477  while (!in.fail() && !in.eof()) {
478  if (!line.empty() && line[0] == '/') {
479  size_t slash = line.find('/', 1);
480  if (slash != string::npos) {
481  string filename = line.substr(1, slash - 1);
482 
483  if (line.substr(slash + 1, 2) == "-1") {
484  // If the first number after the slash is -1, the file used
485  // to be here but was recently cvs removed. It counts as no
486  // longer being an element.
487  } else {
488  cvs_elements.insert(filename);
489  }
490  }
491  }
492 
493  getline(in, line);
494  }
495 
496  return true;
497 }
498 
499 ////////////////////////////////////////////////////////////////////
500 // Function: SoftCVS::scan_scene_file
501 // Access: Private
502 // Description: Reads a scene file, looking for references to element
503 // files. For each reference found, increments the
504 // appropriate element file's reference count.
505 ////////////////////////////////////////////////////////////////////
506 bool SoftCVS::
507 scan_scene_file(istream &in, Multifile &multifile) {
508  bool okflag = true;
509 
510  int c = in.get();
511  while (!in.eof() && !in.fail()) {
512  // Skip whitespace.
513  while (isspace(c) && !in.eof() && !in.fail()) {
514  c = in.get();
515  }
516 
517  // Now begin a word.
518  string word;
519  while (!isspace(c) && !in.eof() && !in.fail()) {
520  word += c;
521  c = in.get();
522  }
523 
524  if (!word.empty()) {
525  SoftFilename v("", word);
526 
527  // Increment the use count on all matching elements of the multiset.
528  pair<ElementFiles::iterator, ElementFiles::iterator> range;
529  range = _element_files.equal_range(v);
530 
531  ElementFiles::iterator ei;
532  for (ei = range.first; ei != range.second; ++ei) {
533  // We cheat and get a non-const reference to the filename out
534  // of the set. We can safely do this because incrementing the
535  // use count won't change its position in the set.
536  SoftFilename &sf = (SoftFilename &)(*ei);
537  sf.increment_use_count();
538 
539  Filename file(sf.get_dirname(), sf.get_filename());
540  if (multifile.update_subfile(file, file, 6).empty()) {
541  nout << "Unable to add " << file << "\n";
542  okflag = false;
543  }
544  }
545  }
546  }
547 
548  return okflag;
549 }
550 
551 ////////////////////////////////////////////////////////////////////
552 // Function: SoftCVS::cvs_add
553 // Access: Private
554 // Description: Invokes CVS to add just the named file to the
555 // repository. Returns true on success, false on
556 // failure.
557 ////////////////////////////////////////////////////////////////////
558 bool SoftCVS::
559 cvs_add(const string &path) {
560  string command = _cvs_binary + " add -kb " + path;
561  nout << command << "\n";
562  int result = system(command.c_str());
563 
564  if (result != 0) {
565  nout << "Failure invoking cvs.\n";
566  return false;
567  }
568  return true;
569 }
570 
571 ////////////////////////////////////////////////////////////////////
572 // Function: SoftCVS::cvs_add_or_remove
573 // Access: Private
574 // Description: Invokes CVS to add (or remove) all of the files in
575 // the indicated vector. Returns true on success, false
576 // on failure.
577 ////////////////////////////////////////////////////////////////////
578 bool SoftCVS::
579 cvs_add_or_remove(const string &cvs_command, const vector_string &paths) {
580  static const int max_command = 4096;
581 
582  if (!paths.empty()) {
583  string command = _cvs_binary + " " + cvs_command;
584  vector_string::const_iterator pi;
585  pi = paths.begin();
586  while (pi != paths.end()) {
587  const string &path = (*pi);
588 
589  if ((int)command.length() + 1 + (int)path.length() >= max_command) {
590  // Fire off the command now.
591  nout << command << "\n";
592  int result = system(command.c_str());
593 
594  if (result != 0) {
595  nout << "Failure invoking cvs.\n";
596  return false;
597  }
598 
599  command = _cvs_binary + " " + cvs_command;
600  }
601 
602  command += ' ';
603  command += path;
604 
605  ++pi;
606  }
607  nout << command << "\n";
608  int result = system(command.c_str());
609 
610  if (result != 0) {
611  nout << "Failure invoking cvs.\n";
612  return false;
613  }
614  }
615  return true;
616 }
617 
618 
619 int main(int argc, char *argv[]) {
620  // A call to pystub() to force libpystub.so to be linked in.
621  pystub();
622 
623  SoftCVS prog;
624  prog.parse_command_line(argc, argv);
625  prog.run();
626  return 0;
627 }
bool is_directory() const
Returns true if the filename exists and is a directory name, false otherwise.
Definition: filename.cxx:1456
bool get_in_cvs() const
Returns true if this file is known to be entered in the CVS database, false if it is not...
bool get_wants_cvs() const
Returns true if this file should be entered into the CVS database, false otherwise.
virtual void parse_command_line(int argc, char **argv)
Dispatches on each of the options on the command line, and passes the remaining parameters to handle_...
void set_text()
Indicates that the filename represents a text file.
Definition: filename.I:507
bool open_read(ifstream &stream) const
Opens the indicated ifstream for reading the file, if possible.
Definition: filename.cxx:2003
bool scan_directory(vector_string &contents) const
Attempts to open the named filename as if it were a directory and looks for the non-hidden files with...
Definition: filename.cxx:1854
string update_subfile(const string &subfile_name, const Filename &filename, int compression_level)
Adds a file on disk to the subfile.
Definition: multifile.cxx:564
bool repack()
Forces a complete rewrite of the Multifile and all of its contents, so that its index will appear at ...
Definition: multifile.cxx:1470
const string & get_base() const
Returns the base part of the filename.
void increment_use_count()
Indicates that this filename is referenced by one more scene file.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
bool open_read_write(const Filename &multifile_name)
Opens the named Multifile on disk for reading and writing.
Definition: multifile.cxx:303
bool flush()
Writes all contents of the Multifile to disk.
Definition: multifile.cxx:1309
bool is_1_0() const
Returns true if this is a version 1_0 filename, false otherwise.
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
const string & get_dirname() const
Returns the name of the directory this file was found in.
This encapsulates a SoftImage versioned filename, of the form base.v-v.ext: it consists of a director...
Definition: softFilename.h:31
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
A file that contains a set of files.
Definition: multifile.h:34
int get_use_count() const
Returns the number of scene files that referenced this filename.
void make_1_0()
Makes this a 1_0 filename.
This program prepares a SoftImage database for CVS by renaming everything to version 1-0...
Definition: softCVS.h:37
bool needs_repack() const
Returns true if the Multifile index is suboptimal and should be repacked.
Definition: multifile.I:71
const string & get_filename() const
Returns the actual filename as found in the directory.
string get_1_0_filename() const
Returns what the filename would be if it were version 1-0.