nufe
[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   bool rooted;
278   filename(sub_path).separate(rooted, pieces);
279   for (int i = 0; i < pieces.length(); i++) {
280     filename_tree *curr = dynamic_cast<filename_tree *>(scanning.current());
281 #ifdef DEBUG_DIRECTORY_TREE
282     LOG(astring("at ") + curr->_dirname.raw());
283 #endif
284     string_array sub_pieces = pieces.subarray(i, i);
285     filename curr_path;
286     curr_path.join(rooted, sub_pieces);
287     curr_path = filename(curr->_dirname.raw() + filename::default_separator()
288         + curr_path.raw());
289 #ifdef DEBUG_DIRECTORY_TREE
290     LOG(astring("made curr path ") + curr_path.raw());
291 #endif
292     if (!curr) return false;
293     bool found_it = false;
294     for (int j = 0; j < curr->branches(); j++) {
295       filename_tree *sub = dynamic_cast<filename_tree *>(curr->branch(j));
296 #ifdef DEBUG_DIRECTORY_TREE
297       LOG(astring("looking at ") + sub->_dirname.raw());
298 #endif
299       if (sub->_dirname.compare_prefix(curr_path)) {
300         // this part matches!
301         scanning.push(sub);
302 #ifdef DEBUG_DIRECTORY_TREE
303         LOG(astring("found at ") + sub->_dirname.raw());
304 #endif
305         found_it = true;
306         break;
307       }
308     }
309     if (!found_it) {
310 #ifdef DEBUG_DIRECTORY_TREE
311       LOG(astring("could not find ") + curr_path.raw());
312 #endif
313       return false;
314     }
315   }
316   return true;
317 }
318
319 filename_tree *directory_tree::goto_current(dir_tree_iterator &scanning)
320 {
321   if (!scanning._current) {
322     // this one hasn't been advanced yet, or it's already over with.
323     scanning._current = (filename_tree *)scanning.next();
324   }
325   // now check that we're healthy.
326   if (!scanning._current) return NIL;  // all done.
327
328   // cast the tree to the right type.
329   return dynamic_cast<filename_tree *>(scanning._current);
330 }
331
332 bool directory_tree::current_dir(dir_tree_iterator &scanning,
333     filename &dir_name)
334 {
335   dir_name = astring::empty_string();
336   filename_tree *tof = goto_current(scanning);
337   if (!tof) return false;
338   dir_name = tof->_dirname;
339   return true;
340 }
341
342 bool directory_tree::current(dir_tree_iterator &scanning,
343     filename &dir_name, string_array &to_fill)
344 {
345   // clear any existing junk.
346   dir_name = astring::empty_string();
347   to_fill.reset();
348
349   filename_tree *tof = goto_current(scanning);
350   if (!tof) return false;
351
352   // fill in what they wanted.
353   dir_name = tof->_dirname;
354   tof->_files.fill(to_fill);
355
356   return true;
357 }
358
359 bool directory_tree::current(dir_tree_iterator &scanning,
360     filename &dir_name, filename_list &to_fill)
361 {
362   // clear any existing junk.
363   dir_name = astring::empty_string();
364   to_fill.reset();
365
366   filename_tree *tof = goto_current(scanning);
367   if (!tof) return false;
368
369   // fill in what they wanted.
370   dir_name = tof->_dirname;
371   to_fill = tof->_files;
372
373   return true;
374 }
375
376 filename_list *directory_tree::access(dir_tree_iterator &scanning)
377 {
378   filename_tree *tof = goto_current(scanning);
379   if (!tof) return NIL;
380   return &tof->_files;
381 }
382
383 bool directory_tree::depth(dir_tree_iterator &scanning, int &depth)
384 {
385   depth = -1;  // invalid as default.
386   filename_tree *tof = goto_current(scanning);
387   if (!tof) return false;
388   depth = tof->_depth;
389   return true;
390 }
391
392 bool directory_tree::children(dir_tree_iterator &scanning, int &kids)
393 {
394   kids = -1;  // invalid as default.
395   filename_tree *tof = goto_current(scanning);
396   if (!tof) return false;
397   kids = tof->branches();
398   return true;
399 }
400
401 bool directory_tree::next(dir_tree_iterator &scanning)
402 {
403   scanning._current = (filename_tree *)scanning.next();
404   return !!scanning._current;
405 }
406
407 void directory_tree::throw_out(dir_tree_iterator * &to_whack)
408 {
409   WHACK(to_whack);
410 }
411
412 filename_tree *directory_tree::seek(const astring &dir_name_in,
413     bool ignore_initial) const
414 {
415   FUNCDEF("seek");
416   array<filename_tree *> examining;
417     // the list of nodes we're currently looking at.
418
419 #ifdef DEBUG_DIRECTORY_TREE
420   LOG(astring("seeking on root of: ") + *_path);
421 #endif
422
423   astring dir_name = filename(dir_name_in).raw();
424   // set the search path up to have the proper prefix.
425   if (ignore_initial)
426     dir_name = path() + filename::default_separator()
427        + filename(dir_name_in).raw();
428
429 #ifdef DEBUG_DIRECTORY_TREE
430   LOG(astring("adding root: ") + _real_tree->_dirname);
431 #endif
432   examining += _real_tree;
433
434   astring sequel;  // holds extra pieces from filename comparisons.
435
436   // chew on the list of nodes to examine until we run out.
437   while (examining.length()) {
438     int posn;
439     bool found = false;
440     // start looking at all the items in the list, even though we might have
441     // to abandon the iteration if we find a match.
442     filename_tree *check = NIL;
443     for (posn = 0; posn < examining.length(); posn++) {
444       check = examining[posn];
445       filename current(check->_dirname);
446       if (!current.is_normal()) {
447 //#ifdef DEBUG_DIRECTORY_TREE
448         LOG(astring("seek: skipping abnormal dir: \"") + current + "\" with came from " + check->class_name());
449 //#endif
450         continue;
451       }
452 #ifdef DEBUG_DIRECTORY_TREE
453       LOG(astring("looking at ") + current.raw());
454 #endif
455       if (current.compare_prefix(dir_name, sequel)) {
456         // we have a match!
457 #ifdef DEBUG_DIRECTORY_TREE
458         LOG(astring("matched! at ") + current.raw());
459 #endif
460         found = true;
461         if (!sequel) {
462           // whoa!  an exact match.  we're done now.
463 #ifdef DEBUG_DIRECTORY_TREE
464           LOG(astring("exact match at ") + current.raw() + "!  done!!!");
465 #endif
466           return check;
467         } else {
468 #ifdef DEBUG_DIRECTORY_TREE
469           LOG(astring("inexact match because sequel=") + sequel);
470 #endif
471         }
472         break;
473       }
474     }
475     if (!found) return NIL;  // we found nothing comparable.
476
477     // we found a partial match.  that means we should start looking at this
478     // node's children for the exact match.
479     if (!check) {
480       // this is a serious logical error!
481       LOG("serious logical error: tree was not located.");
482       return NIL;
483     }
484     examining.reset();  // clear the existing nodes.
485     for (int i = 0; i < check->branches(); i++)
486       examining += (filename_tree *)check->branch(i);
487   }
488
489   return NIL;  // we found nothing that looked like that node.
490 }
491
492 bool directory_tree::calculate(bool just_size)
493 { return calculate(_real_tree, just_size); }
494
495 bool directory_tree::calculate(filename_tree *start, bool just_size)
496 {
497   FUNCDEF("calculate");
498   dir_tree_iterator *ted = start_at(start, directory_tree::postfix);
499     // create our iterator to do a postfix traversal.  why postfix?  well,
500     // prefix has been used elsewhere and since it doesn't really matter what
501     // order we visit the nodes here, it's good to change up.
502
503   int depth;  // current depth in tree.
504   filename curr;  // the current path the iterator is at.
505   filename_list *files;  // the filenames held at the iterator.
506
507   while (directory_tree::current_dir(*ted, curr)) {
508     if (!curr.is_normal()) {
509 //#ifdef DEBUG_DIRECTORY_TREE
510       LOG(astring("calculate: skipping abnormal dir: \"") + curr + "\"");
511 //#endif
512       directory_tree::next(*ted);
513       continue;  // scary non-simple file type.
514     }
515     // we have a good directory to show.
516 #ifdef DEBUG_DIRECTORY_TREE
517     LOG(astring("calcing node ") + curr.raw());
518 #endif
519     files = directory_tree::access(*ted);
520     directory_tree::depth(*ted, depth);
521     for (int i = 0; i < files->elements(); i++) {
522       filename curr_file = curr + "/" + *files->borrow(i);
523 #ifdef DEBUG_DIRECTORY_TREE
524       LOG(astring("got a curr file: ") + curr_file);
525 #endif
526       if (!curr_file.is_normal()) {
527 //#ifdef DEBUG_DIRECTORY_TREE
528         LOG(astring("skipping abnormal file: \"") + curr + "\"");
529 //#endif
530         continue;
531       }
532       if (!files->borrow(i)->calculate(curr.raw(), just_size)) {
533         LOG(astring("failure to calculate ") + files->get(i)->text_form());
534       }
535     }
536
537     directory_tree::next(*ted);
538   }
539
540   directory_tree::throw_out(ted);
541   return true;
542 }
543
544 bool directory_tree::compare_trees(const directory_tree &source,
545     const directory_tree &target, filename_list &differences,
546     file_info::file_similarity how_to_compare)
547 {
548   return compare_trees(source, astring::empty_string(), target,
549       astring::empty_string(), differences, how_to_compare);
550 }
551
552 bool directory_tree::compare_trees(const directory_tree &source,
553     const astring &source_start_in, const directory_tree &target,
554     const astring &target_start_in, filename_list &differences,
555     file_info::file_similarity how_compare)
556 {
557   FUNCDEF("compare_trees");
558   differences.reset();  // prepare it for storage.
559
560   // make sure we get canonical names to work with.
561   filename source_start(source_start_in);
562   filename target_start(target_start_in);
563
564   dir_tree_iterator *ted = source.start(directory_tree::prefix);
565     // create our iterator to do a prefix traversal.
566
567   astring real_source_start = source.path();
568   if (source_start.raw().t()) {
569     // move to the right place.
570     real_source_start = real_source_start + filename::default_separator()
571         + source_start.raw();
572     if (!directory_tree::jump_to(*ted, source_start.raw())) {
573       // can't even start comparing.
574       LOG(astring("failed to find source start in tree, given as ")
575           + source_start.raw());
576       return false;
577     }
578   }
579
580   filename curr;  // the current path the iterator is at.
581   filename_list files;  // the filenames held at the iterator.
582
583   // calculate where our comparison point is on the source.
584   int source_pieces = 0;
585   {
586     string_array temp;
587     bool rooted_source;
588     filename(real_source_start).separate(rooted_source, temp);
589     source_pieces = temp.length();
590   }
591
592   bool seen_zero_pieces = false;
593   while (directory_tree::current(*ted, curr, files)) {
594     // we're in a place in the source tree now.  let's compare it with the
595     // target's recollection.
596
597 #ifdef DEBUG_DIRECTORY_TREE
598     LOG(astring("curr dir in tree: ") + curr.raw());
599 #endif
600
601     string_array pieces;
602     bool curr_rooted;
603     curr.separate(curr_rooted, pieces);  // get the components of the current location.
604 #ifdef DEBUG_DIRECTORY_TREE
605     LOG(astring("name in pieces:") + pieces.text_form());
606 #endif
607     pieces.zap(0, source_pieces - 1);
608       // snap the root components out of there.
609
610     filename corresponding_name;
611 //hmmm: is that right decision?
612     corresponding_name.join(false, pieces);
613 #ifdef DEBUG_DIRECTORY_TREE
614     LOG(astring("computed target name as: ") + corresponding_name);
615 #endif
616     filename original_correspondence(corresponding_name);
617
618     if (!corresponding_name.raw().t()) {
619       if (seen_zero_pieces) {
620 #ifdef DEBUG_DIRECTORY_TREE
621         LOG(astring("breaking out now due to empty correspondence"));
622 #endif
623         break;
624       }
625       seen_zero_pieces = true;
626     }
627     if (target_start.raw().t()) {
628       corresponding_name = filename(target_start.raw()
629           + filename::default_separator() + corresponding_name.raw());
630 /*doesn't help, not right yet.    } else {
631       // if they didn't give us a place to start, we start at the top.
632       corresponding_name = filename(target.path()
633           + filename::default_separator() + corresponding_name.raw());
634 */
635     }
636 #ifdef DEBUG_DIRECTORY_TREE
637     LOG(astring("target with start is: ") + corresponding_name);
638 #endif
639
640     filename_tree *target_now = target.seek(corresponding_name.raw(), true);
641     if (!target_now) {
642       // that entire sub-tree is missing.  add all of the files here into
643       // the list.
644 #ifdef DEBUG_DIRECTORY_TREE
645       LOG(astring("could not find dir in target for ") + curr.raw()
646           + " which we computed corresp as " + corresponding_name.raw());
647 #endif
648     }
649
650     // now scan across all the files that are in our source list.
651     for (int i = 0; i < files.elements(); i++) {
652       if (!target_now  // there was no node, so we're adding everything...
653           || !target_now->_files.member_with_state(*files[i], how_compare) ) {
654         // ... or we need to add this file since it's missing.
655 #ifdef DEBUG_DIRECTORY_TREE
656         LOG(astring("adding record: ") + files[i]->text_form());
657 #endif
658
659         file_info *new_record = new file_info(*files[i]);
660         // record the file time for use later in saving.
661         new_record->calculate(curr, true);
662         astring original = new_record->raw();
663 #ifdef DEBUG_DIRECTORY_TREE
664         LOG(astring("current: ") + new_record->raw());
665 #endif
666
667         astring actual_name = source_start.raw();
668 #ifdef DEBUG_DIRECTORY_TREE
669         if (actual_name.t()) LOG(astring("sname=") + actual_name);
670 #endif
671         if (actual_name.length()) actual_name += filename::default_separator();
672         actual_name += original_correspondence.raw();
673         if (actual_name.length()) actual_name += filename::default_separator();
674         actual_name += new_record->raw();
675 #ifdef DEBUG_DIRECTORY_TREE
676         if (actual_name.t()) LOG(astring("sname=") + actual_name);
677 #endif
678         (filename &)(*new_record) = filename(actual_name);
679
680         astring targ_name = corresponding_name.raw();
681 #ifdef DEBUG_DIRECTORY_TREE
682         if (targ_name.t()) LOG(astring("tname=") + targ_name);
683 #endif
684         if (targ_name.length()) targ_name += filename::default_separator();
685         targ_name += original;
686 #ifdef DEBUG_DIRECTORY_TREE
687         if (targ_name.t()) LOG(astring("tname=") + targ_name);
688 #endif
689
690         new_record->secondary(targ_name);
691
692         differences += new_record;
693 #ifdef DEBUG_DIRECTORY_TREE
694         LOG(astring("came out as: ") + new_record->text_form());
695 #endif
696       }
697     }
698     
699     // go to the next place.
700     directory_tree::next(*ted);
701   }
702
703   directory_tree::throw_out(ted);
704
705   return true;
706 }
707
708 outcome directory_tree::find_common_root(const astring &finding, bool exists,
709     filename_tree * &found, astring &reassembled, string_array &pieces,
710     int &match_place)
711 {
712   FUNCDEF("find_common_root");
713   // test the path to find what it is.
714   filename adding(finding);
715   if (exists && !adding.good())
716     return common::BAD_INPUT;  // not a good path.
717   int file_subtract = 0;  // if it's a file, then we remove last component.
718   if (exists && !adding.is_directory()) file_subtract = 1;
719
720   // break up the path into pieces.
721   pieces.reset();
722   bool rooted;
723   adding.separate(rooted, pieces);
724
725   // break up our root into pieces; we must take off components that are
726   // already in the root.
727   string_array root_pieces;
728   bool root_rooted;
729   filename temp_file(path());
730   temp_file.separate(root_rooted, root_pieces);
731
732   // locate the last place where the path we were given touches our tree.
733   // it could be totally new, partially new, or already contained.
734   filename_tree *last_match = _real_tree;  // where the common root is located.
735   int list_length = pieces.length() - file_subtract;
736   reassembled = "";
737
738   // we must put all the pieces in that already come from the root.
739   for (int i = 0; i < root_pieces.length() - 1; i++) {
740     bool add_slash = false;
741     if (reassembled.length() && (reassembled[reassembled.end()] != '/') )
742       add_slash = true;
743     if (add_slash) reassembled += "/";
744     reassembled += pieces[i];
745     if (reassembled[reassembled.end()] == ':') {
746 #ifdef DEBUG_DIRECTORY_TREE
747       LOG(astring("skipping drive component ") + reassembled);
748 #endif
749       continue;
750     }
751   }
752
753 #ifdef DEBUG_DIRECTORY_TREE
754   LOG(astring("after pre-assembly, path is ") + reassembled);
755 #endif
756
757   outcome to_return = common::NOT_FOUND;
758
759   for (match_place = root_pieces.length() - 1; match_place < list_length;
760       match_place++) {
761     // add a slash if there's not one present already.
762     bool add_slash = false;
763     if (reassembled.length() && (reassembled[reassembled.end()] != '/') )
764       add_slash = true;
765     // add the next component in to our path.
766     if (add_slash) reassembled += "/";
767     reassembled += pieces[match_place];
768     // special case for dos paths.
769     if (reassembled[reassembled.end()] == ':') {
770 #ifdef DEBUG_DIRECTORY_TREE
771       LOG(astring("skipping drive component ") + reassembled);
772 #endif
773       continue;
774     }
775     reassembled = filename(reassembled).raw();  // force compliance with OS.
776 #ifdef DEBUG_DIRECTORY_TREE
777     LOG(astring("now seeking ") + reassembled);
778 #endif
779     filename_tree *sought = seek(reassembled, false);
780     if (!sought) {
781 #ifdef DEBUG_DIRECTORY_TREE
782       LOG(astring("couldn't find ") + reassembled);
783 #endif
784       if (!exists && (match_place == list_length - 1)) {
785         // see if we can get a match on a file rather than a directory, but
786         // only if we're near the end of the compare.
787         if (last_match->_files.member(pieces[match_place])) {
788           // aha!  a file match.
789           to_return = common::OKAY;
790           match_place--;
791           break;
792         }
793       }
794       match_place--;
795       break;
796     } else {
797       // record where we last had some success.
798 #ifdef DEBUG_DIRECTORY_TREE
799       LOG(astring("found subtree for ") + reassembled);
800 #endif
801       last_match = sought;
802     }
803   }
804   // this is a success, but our loop structure can put us one past the right
805   // place.
806   if (match_place >= list_length) {
807     match_place = list_length - 1;
808     to_return = common::OKAY;
809   }
810
811   found = last_match;
812   return to_return;
813 }
814
815 outcome directory_tree::add_path(const astring &new_item, bool just_size)
816 {
817   FUNCDEF("add_path");
818   // test the path to find out what it is.
819   filename adding(new_item);
820   if (!adding.good()) {
821     LOG(astring("non-existent new item!  ") + new_item);
822     return common::NOT_FOUND;  // not an existing path.
823   }
824   if (!adding.is_normal()) {
825 //#ifdef DEBUG_DIRECTORY_TREE
826     LOG(astring("abnormal new item:  ") + new_item);
827 //#endif
828     return common::BAD_INPUT;  // not a good path.
829   }
830   int file_subtract = 0;  // if it's a file, then we remove last component.
831   if (!adding.is_directory()) file_subtract = 1;
832 #ifdef DEBUG_DIRECTORY_TREE
833   if (file_subtract) { LOG(astring("adding a file ") + new_item); }
834   else { LOG(astring("adding a directory ") + new_item); }
835 #endif
836
837   // find the common root, break up the path into pieces, and tell us where
838   // we matched.
839   string_array pieces;
840   filename_tree *last_match = NIL;
841   int comp_index;
842   astring reassembled;  // this will hold the common root.
843   outcome ret = find_common_root(new_item, true, last_match, reassembled,
844       pieces, comp_index);
845   if (!last_match) {
846     LOG(astring("serious error finding common root for ") + new_item
847         + ", got NIL tree.");
848     return common::FAILURE;  // something serious isn't right.
849   }
850
851   if (!file_subtract) {
852     if (ret != common::OKAY) {
853       // if it's a new directory, we add a new node for traverse to work on.
854 #ifdef DEBUG_DIRECTORY_TREE
855       LOG(astring("now adding node for ") + reassembled);
856 #endif
857       filename_tree *new_branch = new filename_tree;
858       new_branch->_depth = last_match->_depth + 1;
859       last_match->attach(new_branch);
860       last_match = new_branch;
861     } else {
862 #ifdef DEBUG_DIRECTORY_TREE
863       LOG(astring("matched properly.  reassembled set to ") + reassembled);
864 #endif
865     }
866   }
867
868   if (file_subtract) {
869     if (ret != common::OKAY) {
870 #ifdef DEBUG_DIRECTORY_TREE
871       LOG(astring("common gave us posn of: ") + reassembled);
872 #endif
873       // handle the case for files now that we have our proper node.
874       string_array partial_pieces;
875       bool partial_rooted;
876       filename(reassembled).separate(partial_rooted, partial_pieces);
877       int levels_missing = pieces.length() - partial_pieces.length();
878
879       // we loop over all the pieces that were missing in between the last
880       // common root and the file's final location.
881       for (int i = 0; i < levels_missing; i++) {
882 #ifdef DEBUG_DIRECTORY_TREE
883         LOG(astring("adding intermediate directory: ") + reassembled);
884 #endif
885         filename_tree *new_branch = new filename_tree;
886         new_branch->_depth = last_match->_depth + 1;
887         new_branch->_dirname = filename(reassembled).raw();
888         last_match->attach(new_branch);
889         last_match = new_branch;
890         reassembled += astring("/") + pieces[partial_pieces.length() + i];
891         reassembled = filename(reassembled).raw();  // canonicalize.
892       }
893     }
894
895     if (!last_match->_files.find(pieces[pieces.last()])) {
896 #ifdef DEBUG_DIRECTORY_TREE
897       LOG(astring("adding new file ") + pieces[pieces.last()]
898         + " at " + reassembled);
899 #endif
900       file_info *to_add = new file_info(pieces[pieces.last()], 0);
901       to_add->calculate(reassembled, just_size);
902       last_match->_files += to_add;
903     } else {
904 #ifdef DEBUG_DIRECTORY_TREE
905       LOG(astring("not adding existing file ") + pieces[pieces.last()]
906           + " at " + reassembled);
907 #endif
908     }
909   } else {
910     // handle the case for directories.
911 #ifdef DEBUG_DIRECTORY_TREE
912     LOG(astring("doing traverse in ") + last_match->_dirname
913         + " to add " + reassembled);
914 #endif
915     traverse(reassembled, "*", *last_match);
916 //hmmm: maybe provide pattern capability instead of assuming all files.
917     calculate(last_match, just_size);
918   }
919
920   return common::OKAY;
921 }
922
923 outcome directory_tree::remove_path(const astring &zap_item)
924 {
925   FUNCDEF("remove_path");
926   // find the common root, if one exists.  if not, we're not going to do this.
927   string_array pieces;
928   filename_tree *last_match = NIL;
929   int comp_index;
930   astring reassembled;
931   outcome ret = find_common_root(zap_item, false, last_match, reassembled,
932       pieces, comp_index);
933   if (!last_match) return common::NOT_FOUND;
934   // if we didn't actually finish iterating to the file, then we're not
935   // whacking anything.
936   if (ret != common::OKAY) {
937 #ifdef DEBUG_DIRECTORY_TREE
938     LOG(astring("got error seeking ") + zap_item + " of "
939         + common::outcome_name(ret));
940 #endif
941     return ret;
942   }
943
944   if (comp_index == pieces.last()) {
945     // if the names match fully, then we're talking about a directory.
946 #ifdef DEBUG_DIRECTORY_TREE
947     LOG(astring("found directory match for ") + zap_item);
948 #endif
949   } else {
950 #ifdef DEBUG_DIRECTORY_TREE
951     LOG(astring("may have found file match for ") + zap_item);
952 #endif
953     filename to_seek(pieces[pieces.last()]);
954     if (!last_match->_files.member(to_seek)) {
955       // this file is not a member, so we must say it's not found.
956 #ifdef DEBUG_DIRECTORY_TREE
957       LOG(astring("couldn't find file match in common root for ") + zap_item);
958 #endif
959       return common::NOT_FOUND;
960     } else {
961       int indy = last_match->_files.locate(to_seek);
962 #ifdef DEBUG_DIRECTORY_TREE
963       LOG(astring("found match to remove for ") + zap_item);
964 #endif
965       last_match->_files.zap(indy, indy);
966       return common::OKAY;  // done!
967     }
968   }
969
970 #ifdef DEBUG_DIRECTORY_TREE
971   LOG(astring("going to whack node at: ") + last_match->_dirname.raw());
972 #endif
973
974   // we're whacking directories, so we need to take out last_match and below.
975   filename_tree *parent = (filename_tree *)last_match->parent();
976   if (!parent || (last_match == _real_tree)) {
977     // this seems to be matching the whole tree.  we disallow that.
978 #ifdef DEBUG_DIRECTORY_TREE
979     LOG("there's a problem whacking this node; it's the root.");
980 #endif
981     return common::BAD_INPUT;
982   }
983 #ifdef DEBUG_DIRECTORY_TREE
984   LOG(astring("pruning tree at ") + last_match->_dirname.raw());
985 #endif
986   parent->prune(last_match);
987   WHACK(last_match);
988
989   return common::OKAY;
990 }
991
992 } //namespace.
993
994