68b82b7fc7d3cdbf41e27182acf178596f6a0d86
[feisty_meow.git] / nucleus / library / filesystem / directory_tree.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : directory_tree                                                    *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 2004-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "directory.h"
16 #include "directory_tree.h"
17 #include "filename.h"
18 #include "filename_list.h"
19 #include "filename_tree.h"
20
21 #include <basis/functions.h>
22 #include <basis/contracts.h>
23 #include <loggers/program_wide_logger.h>
24 #include <structures/object_packers.h>
25 #include <structures/string_array.h>
26 #include <textual/parser_bits.h>
27 #include <textual/string_manipulation.h>
28
29 #include <stdio.h>
30
31 using namespace basis;
32 using namespace loggers;
33 using namespace nodes;
34 using namespace structures;
35 using namespace textual;
36
37 #define DEBUG_DIRECTORY_TREE
38   // uncomment for noisier version.
39
40 #undef LOG
41 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
42 //printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s())
43
44 //////////////
45
46 namespace filesystem {
47
48 class dir_tree_iterator : public filename_tree::iterator
49 {
50 public:
51   filename_tree *_current;
52
53   dir_tree_iterator(const filename_tree *initial,
54       tree::traversal_directions dir)
55   : filename_tree::iterator(initial, dir), _current(NIL) {}
56 };
57
58 //////////////
59
60 directory_tree::directory_tree()
61 : _scanned_okay(false),
62   _path(new astring),
63   _pattern(new astring),
64   _real_tree(new filename_tree),
65   _ignore_files(false),
66   _creator(new fname_tree_creator)
67 {
68 }
69
70 directory_tree::directory_tree(const astring &path, const char *pattern,
71     bool ignore_files)
72 : _scanned_okay(false),
73   _path(new astring(path)),
74   _pattern(new astring(pattern)),
75   _real_tree(NIL),
76   _ignore_files(ignore_files),
77   _creator(new fname_tree_creator)
78 {
79   reset(path, pattern);
80 }
81
82 directory_tree::~directory_tree()
83 {
84   _scanned_okay = false;
85   WHACK(_path);
86   WHACK(_pattern);
87   WHACK(_real_tree);
88   WHACK(_creator);
89 }
90
91 const astring &directory_tree::path() const { return *_path; }
92
93 int directory_tree::packed_size() const
94 {
95   return 2 * PACKED_SIZE_INT32
96       + _path->packed_size()
97       + _pattern->packed_size()
98       + _real_tree->recursive_packed_size();
99 }
100
101 void directory_tree::pack(byte_array &packed_form) const
102 {
103   attach(packed_form, int(_scanned_okay));
104   attach(packed_form, int(_ignore_files));
105   _path->pack(packed_form);
106   _pattern->pack(packed_form);
107   _real_tree->recursive_pack(packed_form);
108 }
109
110 bool directory_tree::unpack(byte_array &packed_form)
111 {
112   int temp;
113   if (!detach(packed_form, temp)) return false;
114   _scanned_okay = temp;
115   if (!detach(packed_form, temp)) return false;
116   _ignore_files = temp;
117   if (!_path->unpack(packed_form)) return false;
118   if (!_pattern->unpack(packed_form)) return false;
119   WHACK(_real_tree);
120   _real_tree = (filename_tree *)packable_tree::recursive_unpack
121       (packed_form, *_creator);
122   if (!_real_tree) {
123     _real_tree = new filename_tree;  // reset it.
124     return false;
125   }
126   return true;
127 }
128
129 void directory_tree::text_form(astring &target, bool show_files)
130 {
131   dir_tree_iterator *ted = start(directory_tree::prefix);
132     // create our iterator to do a prefix traversal.
133
134   int depth;  // current depth in tree.
135   filename curr;  // the current path the iterator is at.
136   string_array files;  // the filenames held at the iterator.
137
138   while (current(*ted, curr, files)) {
139     // we have a good directory to show.
140     directory_tree::depth(*ted, depth);
141     target += string_manipulation::indentation(depth * 2) + astring("[")
142         + curr.raw() + "]" + parser_bits::platform_eol_to_chars();
143     if (show_files) {
144       astring names;
145       for (int i = 0; i < files.length(); i++) names += files[i] + " ";
146       if (names.length()) {
147         astring split;
148         string_manipulation::split_lines(names, split, depth * 2 + 2);
149         target += split + parser_bits::platform_eol_to_chars();
150       }
151     }
152
153     // go to the next place.
154     next(*ted);
155   }
156
157   throw_out(ted);
158 }
159
160 void directory_tree::traverse(const astring &path, const char *pattern,
161     filename_tree &add_to)
162 {
163   FUNCDEF("traverse");
164   // prepare the current node.
165   add_to._dirname = filename(path, astring::empty_string());
166   add_to._files.reset();
167 #ifdef DEBUG_DIRECTORY_TREE
168   LOG(astring("working on node ") + add_to._dirname);
169 #endif
170
171   // open the directory.
172   directory curr(path, "*");
173   if (!curr.good()) return;
174
175   if (!_ignore_files) {
176     // add all the files to the current node.
177     directory curr_stringent(path, pattern);
178     add_to._files = curr_stringent.files();
179   }
180
181   // now iterate across the directories here and add a sub-node for each one,
182   // and recursively traverse that sub-node also.
183   const string_array &dirs = curr.directories();
184   for (int i = 0; i < dirs.length(); i++) {
185     filename_tree *new_branch = NIL;
186     astring new_path = path + filename::default_separator() + dirs[i];
187 #ifdef DEBUG_DIRECTORY_TREE
188     LOG(astring("seeking path: ") + new_path);
189 #endif
190     if (!filename(new_path).is_normal()) {
191 //#ifdef DEBUG_DIRECTORY_TREE
192       LOG(astring("bailing on weird dir: ") + new_path);
193 //#endif
194       continue;  // only regular directories please.
195     }
196     for (int q = 0; q < add_to.branches(); q++) {
197       filename_tree *curr_kid = (filename_tree *)add_to.branch(q);
198 #ifdef DEBUG_DIRECTORY_TREE
199       LOG(astring("curr kid: ") + curr_kid->_dirname);
200 #endif
201       if (filename(new_path).raw().iequals(filename
202           (curr_kid->_dirname).raw())) {
203         new_branch = curr_kid;
204 #ifdef DEBUG_DIRECTORY_TREE
205         LOG(astring("using existing branch for ") + new_path);
206 #endif
207         break;
208       }
209     }
210     if (!new_branch) {
211 #ifdef DEBUG_DIRECTORY_TREE
212       LOG(astring("adding new branch for ") + new_path);
213 #endif
214       new_branch = new filename_tree;
215       add_to.attach(new_branch);
216       new_branch->_depth = add_to._depth + 1;
217     }
218 #ifdef DEBUG_DIRECTORY_TREE
219     LOG(astring("traversing sub-node ") + new_path);
220 #endif
221     traverse(new_path, pattern, *new_branch);
222   }
223 }
224
225 bool directory_tree::reset(const astring &path, const char *pattern)
226 {
227   FUNCDEF("reset");
228   _scanned_okay = false;
229   WHACK(_real_tree);
230 //  filename temp_path(path);
231 //  temp_path.canonicalize();
232   *_path = path;
233 //temp_path.raw();
234   *_pattern = pattern;
235   _real_tree = new filename_tree;
236
237 LOG(astring("dirtree::reset to path: ") + path);
238
239   // check that the top-level is healthy.
240   directory curr(path, "*");
241   if (!curr.good()) return false;
242     // our only exit condition; other directories might not be accessible
243     // underneath, but the top one must be accessible for us to even start
244     // the scanning.
245
246   traverse(path, pattern, *_real_tree);
247   _scanned_okay = true;;
248   return true;
249 }
250
251 dir_tree_iterator *directory_tree::start_at(filename_tree *start,
252     traversal_types type) const
253 {
254   // translate to the lower level traversal enum.
255   tree::traversal_directions dir = tree::prefix;
256   if (type == infix) dir = tree::infix;
257   else if (type == postfix) dir = tree::postfix;
258
259   return new dir_tree_iterator(start, dir);
260 }
261
262 dir_tree_iterator *directory_tree::start(traversal_types type) const
263 {
264   // translate to the lower level traversal enum.
265   tree::traversal_directions dir = tree::prefix;
266   if (type == infix) dir = tree::infix;
267   else if (type == postfix) dir = tree::postfix;
268
269   return new dir_tree_iterator(_real_tree, dir);
270 }
271
272 bool directory_tree::jump_to(dir_tree_iterator &scanning,
273     const astring &sub_path)
274 {
275   FUNCDEF("jump_to");
276   string_array pieces;
277   filename(sub_path).separate(pieces);
278   for (int i = 0; i < pieces.length(); i++) {
279     filename_tree *curr = dynamic_cast<filename_tree *>(scanning.current());
280 #ifdef DEBUG_DIRECTORY_TREE
281     LOG(astring("at ") + curr->_dirname.raw());
282 #endif
283     string_array sub_pieces = pieces.subarray(i, i);
284     filename curr_path;
285     curr_path.join(sub_pieces);
286     curr_path = filename(curr->_dirname.raw() + filename::default_separator()
287         + curr_path.raw());
288 #ifdef DEBUG_DIRECTORY_TREE
289     LOG(astring("made curr path ") + curr_path.raw());
290 #endif
291     if (!curr) return false;
292     bool found_it = false;
293     for (int j = 0; j < curr->branches(); j++) {
294       filename_tree *sub = dynamic_cast<filename_tree *>(curr->branch(j));
295 #ifdef DEBUG_DIRECTORY_TREE
296       LOG(astring("looking at ") + sub->_dirname.raw());
297 #endif
298       if (sub->_dirname.compare_prefix(curr_path)) {
299         // this part matches!
300         scanning.push(sub);
301 #ifdef DEBUG_DIRECTORY_TREE
302         LOG(astring("found at ") + sub->_dirname.raw());
303 #endif
304         found_it = true;
305         break;
306       }
307     }
308     if (!found_it) {
309 #ifdef DEBUG_DIRECTORY_TREE
310       LOG(astring("could not find ") + curr_path.raw());
311 #endif
312       return false;
313     }
314   }
315   return true;
316 }
317
318 filename_tree *directory_tree::goto_current(dir_tree_iterator &scanning)
319 {
320   if (!scanning._current) {
321     // this one hasn't been advanced yet, or it's already over with.
322     scanning._current = (filename_tree *)scanning.next();
323   }
324   // now check that we're healthy.
325   if (!scanning._current) return NIL;  // all done.
326
327   // cast the tree to the right type.
328   return dynamic_cast<filename_tree *>(scanning._current);
329 }
330
331 bool directory_tree::current_dir(dir_tree_iterator &scanning,
332     filename &dir_name)
333 {
334   dir_name = astring::empty_string();
335   filename_tree *tof = goto_current(scanning);
336   if (!tof) return false;
337   dir_name = tof->_dirname;
338   return true;
339 }
340
341 bool directory_tree::current(dir_tree_iterator &scanning,
342     filename &dir_name, string_array &to_fill)
343 {
344   // clear any existing junk.
345   dir_name = astring::empty_string();
346   to_fill.reset();
347
348   filename_tree *tof = goto_current(scanning);
349   if (!tof) return false;
350
351   // fill in what they wanted.
352   dir_name = tof->_dirname;
353   tof->_files.fill(to_fill);
354
355   return true;
356 }
357
358 bool directory_tree::current(dir_tree_iterator &scanning,
359     filename &dir_name, filename_list &to_fill)
360 {
361   // clear any existing junk.
362   dir_name = astring::empty_string();
363   to_fill.reset();
364
365   filename_tree *tof = goto_current(scanning);
366   if (!tof) return false;
367
368   // fill in what they wanted.
369   dir_name = tof->_dirname;
370   to_fill = tof->_files;
371
372   return true;
373 }
374
375 filename_list *directory_tree::access(dir_tree_iterator &scanning)
376 {
377   filename_tree *tof = goto_current(scanning);
378   if (!tof) return NIL;
379   return &tof->_files;
380 }
381
382 bool directory_tree::depth(dir_tree_iterator &scanning, int &depth)
383 {
384   depth = -1;  // invalid as default.
385   filename_tree *tof = goto_current(scanning);
386   if (!tof) return false;
387   depth = tof->_depth;
388   return true;
389 }
390
391 bool directory_tree::children(dir_tree_iterator &scanning, int &kids)
392 {
393   kids = -1;  // invalid as default.
394   filename_tree *tof = goto_current(scanning);
395   if (!tof) return false;
396   kids = tof->branches();
397   return true;
398 }
399
400 bool directory_tree::next(dir_tree_iterator &scanning)
401 {
402   scanning._current = (filename_tree *)scanning.next();
403   return !!scanning._current;
404 }
405
406 void directory_tree::throw_out(dir_tree_iterator * &to_whack)
407 {
408   WHACK(to_whack);
409 }
410
411 filename_tree *directory_tree::seek(const astring &dir_name_in,
412     bool ignore_initial) const
413 {
414   FUNCDEF("seek");
415   array<filename_tree *> examining;
416     // the list of nodes we're currently looking at.
417
418 #ifdef DEBUG_DIRECTORY_TREE
419   LOG(astring("seeking on root of: ") + *_path);
420 #endif
421
422   astring dir_name = filename(dir_name_in).raw();
423   // set the search path up to have the proper prefix.
424   if (ignore_initial)
425     dir_name = path() + filename::default_separator()
426        + filename(dir_name_in).raw();
427
428 #ifdef DEBUG_DIRECTORY_TREE
429   LOG(astring("adding root: ") + _real_tree->_dirname);
430 #endif
431   examining += _real_tree;
432
433   astring sequel;  // holds extra pieces from filename comparisons.
434
435   // chew on the list of nodes to examine until we run out.
436   while (examining.length()) {
437     int posn;
438     bool found = false;
439     // start looking at all the items in the list, even though we might have
440     // to abandon the iteration if we find a match.
441     filename_tree *check = NIL;
442     for (posn = 0; posn < examining.length(); posn++) {
443       check = examining[posn];
444       filename current(check->_dirname);
445       if (!current.is_normal()) {
446 //#ifdef DEBUG_DIRECTORY_TREE
447         LOG(astring("skipping abnormal dir: \"") + current + "\"");
448 //#endif
449         continue;
450       }
451 #ifdef DEBUG_DIRECTORY_TREE
452       LOG(astring("looking at ") + current.raw());
453 #endif
454       if (current.compare_prefix(dir_name, sequel)) {
455         // we have a match!
456 #ifdef DEBUG_DIRECTORY_TREE
457         LOG(astring("matched! at ") + current.raw());
458 #endif
459         found = true;
460         if (!sequel) {
461           // whoa!  an exact match.  we're done now.
462 #ifdef DEBUG_DIRECTORY_TREE
463           LOG(astring("exact match at ") + current.raw() + "!  done!!!");
464 #endif
465           return check;
466         } else {
467 #ifdef DEBUG_DIRECTORY_TREE
468           LOG(astring("inexact match because sequel=") + sequel);
469 #endif
470         }
471         break;
472       }
473     }
474     if (!found) return NIL;  // we found nothing comparable.
475
476     // we found a partial match.  that means we should start looking at this
477     // node's children for the exact match.
478     if (!check) {
479       // this is a serious logical error!
480       LOG("serious logical error: tree was not located.");
481       return NIL;
482     }
483     examining.reset();  // clear the existing nodes.
484     for (int i = 0; i < check->branches(); i++)
485       examining += (filename_tree *)check->branch(i);
486   }
487
488   return NIL;  // we found nothing that looked like that node.
489 }
490
491 bool directory_tree::calculate(bool just_size)
492 { return calculate(_real_tree, just_size); }
493
494 bool directory_tree::calculate(filename_tree *start, bool just_size)
495 {
496   FUNCDEF("calculate");
497   dir_tree_iterator *ted = start_at(start, directory_tree::postfix);
498     // create our iterator to do a postfix traversal.  why postfix?  well,
499     // prefix has been used elsewhere and since it doesn't really matter what
500     // order we visit the nodes here, it's good to change up.
501
502   int depth;  // current depth in tree.
503   filename curr;  // the current path the iterator is at.
504   filename_list *files;  // the filenames held at the iterator.
505
506   while (directory_tree::current_dir(*ted, curr)) {
507     if (!curr.is_normal()) {
508 //#ifdef DEBUG_DIRECTORY_TREE
509       LOG(astring("skipping abnormal dir: \"") + curr + "\"");
510 //#endif
511       directory_tree::next(*ted);
512       continue;  // scary non-simple file type.
513     }
514     // we have a good directory to show.
515 #ifdef DEBUG_DIRECTORY_TREE
516     LOG(astring("calcing node ") + curr.raw());
517 #endif
518     files = directory_tree::access(*ted);
519     directory_tree::depth(*ted, depth);
520     for (int i = 0; i < files->elements(); i++) {
521       filename curr_file = curr + "/" + *files->borrow(i);
522 #ifdef DEBUG_DIRECTORY_TREE
523       LOG(astring("got a curr file: ") + curr_file);
524 #endif
525       if (!curr_file.is_normal()) {
526 //#ifdef DEBUG_DIRECTORY_TREE
527         LOG(astring("skipping abnormal file: \"") + curr + "\"");
528 //#endif
529         continue;
530       }
531       if (!files->borrow(i)->calculate(curr.raw(), just_size)) {
532         LOG(astring("failure to calculate ") + files->get(i)->text_form());
533       }
534     }
535
536     directory_tree::next(*ted);
537   }
538
539   directory_tree::throw_out(ted);
540   return true;
541 }
542
543 bool directory_tree::compare_trees(const directory_tree &source,
544     const directory_tree &target, filename_list &differences,
545     file_info::file_similarity how_to_compare)
546 {
547   return compare_trees(source, astring::empty_string(), target,
548       astring::empty_string(), differences, how_to_compare);
549 }
550
551 bool directory_tree::compare_trees(const directory_tree &source,
552     const astring &source_start_in, const directory_tree &target,
553     const astring &target_start_in, filename_list &differences,
554     file_info::file_similarity how_compare)
555 {
556   FUNCDEF("compare_trees");
557   differences.reset();  // prepare it for storage.
558
559   // make sure we get canonical names to work with.
560   filename source_start(source_start_in);
561   filename target_start(target_start_in);
562
563   dir_tree_iterator *ted = source.start(directory_tree::prefix);
564     // create our iterator to do a prefix traversal.
565
566   astring real_source_start = source.path();
567   if (source_start.raw().t()) {
568     // move to the right place.
569     real_source_start = real_source_start + filename::default_separator()
570         + source_start.raw();
571     if (!directory_tree::jump_to(*ted, source_start.raw())) {
572       // can't even start comparing.
573       LOG(astring("failed to find source start in tree, given as ")
574           + source_start.raw());
575       return false;
576     }
577   }
578
579   filename curr;  // the current path the iterator is at.
580   filename_list files;  // the filenames held at the iterator.
581
582   // calculate where our comparison point is on the source.
583   int source_pieces = 0;
584   {
585     string_array temp;
586     filename(real_source_start).separate(temp);
587     source_pieces = temp.length();
588   }
589
590   bool seen_zero_pieces = false;
591   while (directory_tree::current(*ted, curr, files)) {
592     // we're in a place in the source tree now.  let's compare it with the
593     // target's recollection.
594
595 #ifdef DEBUG_DIRECTORY_TREE
596     LOG(astring("curr dir in tree: ") + curr.raw());
597 #endif
598
599     string_array pieces;
600     curr.separate(pieces);  // get the components of the current location.
601 #ifdef DEBUG_DIRECTORY_TREE
602     LOG(astring("name in pieces:") + pieces.text_form());
603 #endif
604     pieces.zap(0, source_pieces - 1);
605       // snap the root components out of there.
606
607     filename corresponding_name;
608     corresponding_name.join(pieces);
609 #ifdef DEBUG_DIRECTORY_TREE
610     LOG(astring("computed target name as: ") + corresponding_name);
611 #endif
612     filename original_correspondence(corresponding_name);
613
614     if (!corresponding_name.raw().t()) {
615       if (seen_zero_pieces) {
616 #ifdef DEBUG_DIRECTORY_TREE
617         LOG(astring("breaking out now due to empty correspondence"));
618 #endif
619         break;
620       }
621       seen_zero_pieces = true;
622     }
623     if (target_start.raw().t()) {
624       corresponding_name = filename(target_start.raw()
625           + filename::default_separator() + corresponding_name.raw());
626 /*doesn't help, not right yet.    } else {
627       // if they didn't give us a place to start, we start at the top.
628       corresponding_name = filename(target.path()
629           + filename::default_separator() + corresponding_name.raw());
630 */
631     }
632 #ifdef DEBUG_DIRECTORY_TREE
633     LOG(astring("target with start is: ") + corresponding_name);
634 #endif
635
636     filename_tree *target_now = target.seek(corresponding_name.raw(), true);
637     if (!target_now) {
638       // that entire sub-tree is missing.  add all of the files here into
639       // the list.
640 #ifdef DEBUG_DIRECTORY_TREE
641       LOG(astring("could not find dir in target for ") + curr.raw()
642           + " which we computed corresp as " + corresponding_name.raw());
643 #endif
644     }
645
646     // now scan across all the files that are in our source list.
647     for (int i = 0; i < files.elements(); i++) {
648       if (!target_now  // there was no node, so we're adding everything...
649           || !target_now->_files.member_with_state(*files[i], how_compare) ) {
650         // ... or we need to add this file since it's missing.
651 #ifdef DEBUG_DIRECTORY_TREE
652         LOG(astring("adding record: ") + files[i]->text_form());
653 #endif
654
655         file_info *new_record = new file_info(*files[i]);
656         // record the file time for use later in saving.
657         new_record->calculate(curr, true);
658         astring original = new_record->raw();
659 #ifdef DEBUG_DIRECTORY_TREE
660         LOG(astring("current: ") + new_record->raw());
661 #endif
662
663         astring actual_name = source_start.raw();
664 #ifdef DEBUG_DIRECTORY_TREE
665         if (actual_name.t()) LOG(astring("sname=") + actual_name);
666 #endif
667         if (actual_name.length()) actual_name += filename::default_separator();
668         actual_name += original_correspondence.raw();
669         if (actual_name.length()) actual_name += filename::default_separator();
670         actual_name += new_record->raw();
671 #ifdef DEBUG_DIRECTORY_TREE
672         if (actual_name.t()) LOG(astring("sname=") + actual_name);
673 #endif
674         (filename &)(*new_record) = filename(actual_name);
675
676         astring targ_name = corresponding_name.raw();
677 #ifdef DEBUG_DIRECTORY_TREE
678         if (targ_name.t()) LOG(astring("tname=") + targ_name);
679 #endif
680         if (targ_name.length()) targ_name += filename::default_separator();
681         targ_name += original;
682 #ifdef DEBUG_DIRECTORY_TREE
683         if (targ_name.t()) LOG(astring("tname=") + targ_name);
684 #endif
685
686         new_record->secondary(targ_name);
687
688         differences += new_record;
689 #ifdef DEBUG_DIRECTORY_TREE
690         LOG(astring("came out as: ") + new_record->text_form());
691 #endif
692       }
693     }
694     
695     // go to the next place.
696     directory_tree::next(*ted);
697   }
698
699   directory_tree::throw_out(ted);
700
701   return true;
702 }
703
704 outcome directory_tree::find_common_root(const astring &finding, bool exists,
705     filename_tree * &found, astring &reassembled, string_array &pieces,
706     int &match_place)
707 {
708   FUNCDEF("find_common_root");
709   // test the path to find what it is.
710   filename adding(finding);
711   if (exists && !adding.good())
712     return common::BAD_INPUT;  // not a good path.
713   int file_subtract = 0;  // if it's a file, then we remove last component.
714   if (exists && !adding.is_directory()) file_subtract = 1;
715
716   // break up the path into pieces.
717   pieces.reset();
718   adding.separate(pieces);
719
720   // break up our root into pieces; we must take off components that are
721   // already in the root.
722   string_array root_pieces;
723   filename temp_file(path());
724   temp_file.separate(root_pieces);
725
726   // locate the last place where the path we were given touches our tree.
727   // it could be totally new, partially new, or already contained.
728   filename_tree *last_match = _real_tree;  // where the common root is located.
729   int list_length = pieces.length() - file_subtract;
730   reassembled = "";
731
732   // we must put all the pieces in that already come from the root.
733   for (int i = 0; i < root_pieces.length() - 1; i++) {
734     bool add_slash = false;
735     if (reassembled.length() && (reassembled[reassembled.end()] != '/') )
736       add_slash = true;
737     if (add_slash) reassembled += "/";
738     reassembled += pieces[i];
739     if (reassembled[reassembled.end()] == ':') {
740 #ifdef DEBUG_DIRECTORY_TREE
741       LOG(astring("skipping drive component ") + reassembled);
742 #endif
743       continue;
744     }
745   }
746
747 #ifdef DEBUG_DIRECTORY_TREE
748   LOG(astring("after pre-assembly, path is ") + reassembled);
749 #endif
750
751   outcome to_return = common::NOT_FOUND;
752
753   for (match_place = root_pieces.length() - 1; match_place < list_length;
754       match_place++) {
755     // add a slash if there's not one present already.
756     bool add_slash = false;
757     if (reassembled.length() && (reassembled[reassembled.end()] != '/') )
758       add_slash = true;
759     // add the next component in to our path.
760     if (add_slash) reassembled += "/";
761     reassembled += pieces[match_place];
762     // special case for dos paths.
763     if (reassembled[reassembled.end()] == ':') {
764 #ifdef DEBUG_DIRECTORY_TREE
765       LOG(astring("skipping drive component ") + reassembled);
766 #endif
767       continue;
768     }
769     reassembled = filename(reassembled).raw();  // force compliance with OS.
770 #ifdef DEBUG_DIRECTORY_TREE
771     LOG(astring("now seeking ") + reassembled);
772 #endif
773     filename_tree *sought = seek(reassembled, false);
774     if (!sought) {
775 #ifdef DEBUG_DIRECTORY_TREE
776       LOG(astring("couldn't find ") + reassembled);
777 #endif
778       if (!exists && (match_place == list_length - 1)) {
779         // see if we can get a match on a file rather than a directory, but
780         // only if we're near the end of the compare.
781         if (last_match->_files.member(pieces[match_place])) {
782           // aha!  a file match.
783           to_return = common::OKAY;
784           match_place--;
785           break;
786         }
787       }
788       match_place--;
789       break;
790     } else {
791       // record where we last had some success.
792 #ifdef DEBUG_DIRECTORY_TREE
793       LOG(astring("found subtree for ") + reassembled);
794 #endif
795       last_match = sought;
796     }
797   }
798   // this is a success, but our loop structure can put us one past the right
799   // place.
800   if (match_place >= list_length) {
801     match_place = list_length - 1;
802     to_return = common::OKAY;
803   }
804
805   found = last_match;
806   return to_return;
807 }
808
809 outcome directory_tree::add_path(const astring &new_item, bool just_size)
810 {
811   FUNCDEF("add_path");
812   // test the path to find out what it is.
813   filename adding(new_item);
814   if (!adding.good()) {
815     LOG(astring("non-existent new item!  ") + new_item);
816     return common::NOT_FOUND;  // not an existing path.
817   }
818   if (!adding.is_normal()) {
819 //#ifdef DEBUG_DIRECTORY_TREE
820     LOG(astring("abnormal new item:  ") + new_item);
821 //#endif
822     return common::BAD_INPUT;  // not a good path.
823   }
824   int file_subtract = 0;  // if it's a file, then we remove last component.
825   if (!adding.is_directory()) file_subtract = 1;
826 #ifdef DEBUG_DIRECTORY_TREE
827   if (file_subtract) { LOG(astring("adding a file ") + new_item); }
828   else { LOG(astring("adding a directory ") + new_item); }
829 #endif
830
831   // find the common root, break up the path into pieces, and tell us where
832   // we matched.
833   string_array pieces;
834   filename_tree *last_match = NIL;
835   int comp_index;
836   astring reassembled;  // this will hold the common root.
837   outcome ret = find_common_root(new_item, true, last_match, reassembled,
838       pieces, comp_index);
839   if (!last_match) {
840     LOG(astring("serious error finding common root for ") + new_item
841         + ", got NIL tree.");
842     return common::FAILURE;  // something serious isn't right.
843   }
844
845   if (!file_subtract) {
846     if (ret != common::OKAY) {
847       // if it's a new directory, we add a new node for traverse to work on.
848 #ifdef DEBUG_DIRECTORY_TREE
849       LOG(astring("now adding node for ") + reassembled);
850 #endif
851       filename_tree *new_branch = new filename_tree;
852       new_branch->_depth = last_match->_depth + 1;
853       last_match->attach(new_branch);
854       last_match = new_branch;
855     } else {
856 #ifdef DEBUG_DIRECTORY_TREE
857       LOG(astring("matched properly.  reassembled set to ") + reassembled);
858 #endif
859     }
860   }
861
862   if (file_subtract) {
863     if (ret != common::OKAY) {
864 #ifdef DEBUG_DIRECTORY_TREE
865       LOG(astring("common gave us posn of: ") + reassembled);
866 #endif
867       // handle the case for files now that we have our proper node.
868       string_array partial_pieces;
869       filename(reassembled).separate(partial_pieces);
870       int levels_missing = pieces.length() - partial_pieces.length();
871
872       // we loop over all the pieces that were missing in between the last
873       // common root and the file's final location.
874       for (int i = 0; i < levels_missing; i++) {
875 #ifdef DEBUG_DIRECTORY_TREE
876         LOG(astring("adding intermediate directory: ") + reassembled);
877 #endif
878         filename_tree *new_branch = new filename_tree;
879         new_branch->_depth = last_match->_depth + 1;
880         new_branch->_dirname = filename(reassembled).raw();
881         last_match->attach(new_branch);
882         last_match = new_branch;
883         reassembled += astring("/") + pieces[partial_pieces.length() + i];
884         reassembled = filename(reassembled).raw();  // canonicalize.
885       }
886     }
887
888     if (!last_match->_files.find(pieces[pieces.last()])) {
889 #ifdef DEBUG_DIRECTORY_TREE
890       LOG(astring("adding new file ") + pieces[pieces.last()]
891         + " at " + reassembled);
892 #endif
893       file_info *to_add = new file_info(pieces[pieces.last()], 0);
894       to_add->calculate(reassembled, just_size);
895       last_match->_files += to_add;
896     } else {
897 #ifdef DEBUG_DIRECTORY_TREE
898       LOG(astring("not adding existing file ") + pieces[pieces.last()]
899           + " at " + reassembled);
900 #endif
901     }
902   } else {
903     // handle the case for directories.
904 #ifdef DEBUG_DIRECTORY_TREE
905     LOG(astring("doing traverse in ") + last_match->_dirname
906         + " to add " + reassembled);
907 #endif
908     traverse(reassembled, "*", *last_match);
909 //hmmm: maybe provide pattern capability instead of assuming all files.
910     calculate(last_match, just_size);
911   }
912
913   return common::OKAY;
914 }
915
916 outcome directory_tree::remove_path(const astring &zap_item)
917 {
918   FUNCDEF("remove_path");
919   // find the common root, if one exists.  if not, we're not going to do this.
920   string_array pieces;
921   filename_tree *last_match = NIL;
922   int comp_index;
923   astring reassembled;
924   outcome ret = find_common_root(zap_item, false, last_match, reassembled,
925       pieces, comp_index);
926   if (!last_match) return common::NOT_FOUND;
927   // if we didn't actually finish iterating to the file, then we're not
928   // whacking anything.
929   if (ret != common::OKAY) {
930 #ifdef DEBUG_DIRECTORY_TREE
931     LOG(astring("got error seeking ") + zap_item + " of "
932         + common::outcome_name(ret));
933 #endif
934     return ret;
935   }
936
937   if (comp_index == pieces.last()) {
938     // if the names match fully, then we're talking about a directory.
939 #ifdef DEBUG_DIRECTORY_TREE
940     LOG(astring("found directory match for ") + zap_item);
941 #endif
942   } else {
943 #ifdef DEBUG_DIRECTORY_TREE
944     LOG(astring("may have found file match for ") + zap_item);
945 #endif
946     filename to_seek(pieces[pieces.last()]);
947     if (!last_match->_files.member(to_seek)) {
948       // this file is not a member, so we must say it's not found.
949 #ifdef DEBUG_DIRECTORY_TREE
950       LOG(astring("couldn't find file match in common root for ") + zap_item);
951 #endif
952       return common::NOT_FOUND;
953     } else {
954       int indy = last_match->_files.locate(to_seek);
955 #ifdef DEBUG_DIRECTORY_TREE
956       LOG(astring("found match to remove for ") + zap_item);
957 #endif
958       last_match->_files.zap(indy, indy);
959       return common::OKAY;  // done!
960     }
961   }
962
963 #ifdef DEBUG_DIRECTORY_TREE
964   LOG(astring("going to whack node at: ") + last_match->_dirname.raw());
965 #endif
966
967   // we're whacking directories, so we need to take out last_match and below.
968   filename_tree *parent = (filename_tree *)last_match->parent();
969   if (!parent || (last_match == _real_tree)) {
970     // this seems to be matching the whole tree.  we disallow that.
971 #ifdef DEBUG_DIRECTORY_TREE
972     LOG("there's a problem whacking this node; it's the root.");
973 #endif
974     return common::BAD_INPUT;
975   }
976 #ifdef DEBUG_DIRECTORY_TREE
977   LOG(astring("pruning tree at ") + last_match->_dirname.raw());
978 #endif
979   parent->prune(last_match);
980   WHACK(last_match);
981
982   return common::OKAY;
983 }
984
985 } //namespace.
986
987