1 /*
2 	gentree.cc	Generate Directory Tree For Display
3 	Copyright (c) 2000, 2002, 2003, 2004, 2005, 2009 Kriang Lerdsuwanakij
4 	email:		lerdsuwa@users.sourceforge.net
5 
6 	This program is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 2 of the License, or
9 	(at your option) any later version.
10 
11 	This program is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with this program; if not, write to the Free Software
18 	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20 
21 #include "gentree.h"
22 
23 #include "confobj.h"
24 #include "buffer.h"
25 #include "miscobj.h"
26 #include "khwin.h"
27 #include "dirtree.h"
28 #include "dirutil.h"
29 #include "cstrlib.h"
30 #include "scandir.h"
31 
32 #ifdef HAVE_UNISTD_H
33 # include <unistd.h>
34 #endif
35 
36 //#define DUMP_HTML
37 #ifdef DUMP_HTML
38 # include <fcntl.h>
39 # include <sys/stat.h>
40 # include <sys/types.h>
41 #endif
42 
43 #include CXX__HEADER_algorithm
44 #include CXX__HEADER_fstream
45 
46 string	saveCwd;
47 
48 extern string	dirFile;
49 
my_strftime(const char * format,const struct tm * tm)50 string	my_strftime(const char *format, const struct tm *tm)
51 {
52 	Buffer buf(1000);
53 	strftime(buf.GetPtr(), buf.GetAllocSize(), format, tm);
54 	return string(buf.GetPtr());
55 }
56 
57 /*************************************************************************
58 	Generate commonly used strings
59 *************************************************************************/
60 
61 string	html_unreadable;
62 string	html_skipped;
63 string	html_outside_tree;
64 string	html_not_in_file;
65 string	html_not_found;
66 string	html_no_filelist;
67 
68 size_t	html_unreadable_width;
69 size_t	html_skipped_width;
70 size_t	html_outside_tree_width;
71 size_t	html_not_in_file_width;
72 size_t	html_not_found_width;
73 size_t	html_no_filelist_width;
74 
GenerateHTMLStrings()75 void	GenerateHTMLStrings()
76 {
77 	html_unreadable = _(" [unreadable]");
78 	html_skipped = _(" [skipped]");
79 	html_outside_tree = _(" [outside tree]");
80 	html_not_in_file = _(" [*]");
81 	html_not_found = _(" [not found]");
82 	html_no_filelist = _(" [no filelist]");
83 
84 	html_unreadable_width = GetStringWidth(html_unreadable);
85 	html_skipped_width = GetStringWidth(html_skipped);
86 	html_outside_tree_width = GetStringWidth(html_outside_tree);
87 	html_not_in_file_width = GetStringWidth(html_not_in_file);
88 	html_not_found_width = GetStringWidth(html_not_found);
89 	html_no_filelist_width = GetStringWidth(html_no_filelist);
90 }
91 
92 /*************************************************************************
93 	Generate HTML-like codes from the directory tree
94 *************************************************************************/
95 
96 size_t	htmlRow, htmlCol;
97 
ReplaceControlChar(string & s)98 void	ReplaceControlChar(string &s)
99 {
100 	for (size_t i = 0; i < s.size(); ++i) {
101 						// Characters interpreted by
102 						// DrawString
103 		if (s[i] == '\t' || s[i] == '\r' || s[i] == '\b' || s[i] == '\0'
104 		    || s[i] == '\n')
105 			s[i] = '?';
106 	}
107 }
108 
109 class HyperTreeDraw : public HyperDraw {
110 	private:
111 		sptr_list<DirectoryEntry> *dirTree;
112 
113 		vector<size_t>	barStack;	// Positions of vertical bars
114 		size_t	curCol;			// curRow not needed
115 
116 		void	NewLineUpdateRowCol();
117 
118 		void	GenerateSymLinkHTML(HyperDocument &html, WINDOW *pad,
119 					    sptr<DirectoryEntry> &d,
120 					    const string &new_dir);
121 
122 		void	GenerateDirHTML(HyperDocument &html, WINDOW *pad,
123 					sptr<DirectoryEntry> &d,
124 					const string &new_dir);
125 
126 		void	RecursiveGenerateHTML(HyperDocument &html, WINDOW *pad,
127 					      sptr<DirectoryEntry> &d,
128 					      size_t pos, const string &current_dir);
129 		void	DrawBegin(HyperDocument &html, WINDOW *pad);
130 		void	DrawEnd(HyperDocument &html, WINDOW *pad);
131 	public:
132 		void	Draw(HyperDocument &html, WINDOW *pad);
HyperTreeDraw(sptr_list<DirectoryEntry> * dirTree_)133 		HyperTreeDraw(sptr_list<DirectoryEntry> *dirTree_)
134 				: dirTree(dirTree_) {}
135 };
136 
NewLineUpdateRowCol()137 void	HyperTreeDraw::NewLineUpdateRowCol()
138 {
139 	if (curCol > htmlCol)
140 		htmlCol = curCol;
141 	htmlRow++;
142 	curCol = 0;
143 }
144 
GenerateSymLinkHTML(HyperDocument & html,WINDOW * pad,sptr<DirectoryEntry> & d,const string & new_dir)145 void	HyperTreeDraw::GenerateSymLinkHTML(HyperDocument &html, WINDOW *pad,
146 					   sptr<DirectoryEntry> &d,
147 					   const string &new_dir)
148 {
149 	bool	tagBegin = false;
150 
151 	if (d->isLinkDestSkip == 0
152 	    && d->isLinkDestUnreadable != 1) {
153 
154 						// Write tags
155 		html.DrawAName(pad, new_dir);
156 
157 		string s = "#";
158 		s += d->GetLinkDestStr();	// Anything after '#' can contain any
159 						// char, so quoting URL is not needed.
160 		html.DrawAHrefBegin(pad, s);
161 
162 		html.DrawSetItalic(pad);
163 
164 		tagBegin = true;
165 	}
166 	else if (d->isLinkDestSkip == 1) {
167 						// Write tags
168 		html.DrawAName(pad, new_dir);
169 
170 		html.DrawACallBegin(pad, d->GetLinkDestStr());
171 
172 		html.DrawSetItalic(pad);
173 
174 		tagBegin = true;
175 	}
176 
177 			    			// Write dir. name
178 	string s = d->GetNameStr();
179 	ReplaceControlChar(s);
180 	html.DrawString(pad, s);
181 	curCol += GetStringWidth(s);
182 
183 			    			// Write graphical " -> "
184 	html.DrawChar(pad, ' ');
185 	html.DrawEntityChar(pad, EID_BOXH);
186 	html.DrawEntityChar(pad, EID_RARR);
187 	html.DrawChar(pad, ' ');
188 	curCol += 4;
189 
190 			    			// Write linked dir.
191 	s = d->GetLinkStr();
192 	ReplaceControlChar(s);
193 	html.DrawString(pad, s);
194 	curCol += GetStringWidth(s);
195 
196 	switch(d->isLinkDestSkip) {
197 		case 0:
198 			if(d->isLinkDestUnreadable) {
199 				html.DrawString(pad, html_unreadable);
200 				curCol += html_unreadable_width;
201 			}
202 			break;
203 		case 1:
204 			html.DrawString(pad, html_outside_tree);
205 			curCol += html_outside_tree_width;
206 			break;
207 		case 2:
208 			html.DrawString(pad, html_not_in_file);
209 			curCol += html_not_in_file_width;
210 			break;
211 		case 3:
212 			html.DrawString(pad, html_not_found);
213 			curCol += html_not_found_width;
214 			break;
215 	}
216 
217 	if (tagBegin) {
218 			    			// End <A HREF=...> tag
219 		html.DrawClearItalic(pad);
220 		html.DrawAEnd(pad);
221 	}
222 }
223 
GenerateDirHTML(HyperDocument & html,WINDOW * pad,sptr<DirectoryEntry> & d,const string & new_dir)224 void	HyperTreeDraw::GenerateDirHTML(HyperDocument &html, WINDOW *pad,
225 				       sptr<DirectoryEntry> &d,
226 				       const string &new_dir)
227 {
228 	if (d->isUnreadable != 1) {		// Exec permission
229 						// Write tags
230 		html.DrawAName(pad, new_dir);
231 		html.DrawACallBegin(pad, new_dir);
232 	}
233 //ofstream ofs("xx", ios::app);
234 //if (!pad)
235 //ofs << htmlRow << ' ' << htmlCol << ' ' << curCol << ' ' << d->GetNameStr() << '\n';
236 			    			// Write dir. name
237 	string s = d->GetNameStr();
238 	ReplaceControlChar(s);
239 	html.DrawString(pad, s);
240 	curCol += GetStringWidth(s);
241 
242 	if (d->isUnreadable != 1) {		// Exec permission
243 			    			// Dir. can be accessed
244 
245 			    			// End <A CALL=...> tag
246 		html.DrawAEnd(pad);
247 	}
248 }
249 
RecursiveGenerateHTML(HyperDocument & html,WINDOW * pad,sptr<DirectoryEntry> & d,size_t pos,const string & current_dir)250 void	HyperTreeDraw::RecursiveGenerateHTML(HyperDocument &html, WINDOW *pad,
251 					     sptr<DirectoryEntry> &d,
252 					     size_t pos, const string &current_dir)
253 {
254 	if (d->isSkip) {
255 		if (d->isSkip == 3) {
256 			html.DrawString(pad, html_no_filelist);
257 			curCol += html_no_filelist_width;
258 			pos += html_no_filelist_width;
259 		}
260 		else if (d->isSkip == 2) {
261 			html.DrawString(pad, html_not_in_file);
262 			curCol += html_not_in_file_width;
263 			pos += html_not_in_file_width;
264 		}
265 		else {
266 			html.DrawString(pad, html_skipped);
267 			curCol += html_skipped_width;
268 			pos += html_skipped_width;
269 		}
270 	}
271 
272 	if (d->isUnreadable) {
273 		html.DrawString(pad, html_unreadable);
274 		curCol += html_unreadable_width;
275 		pos += html_unreadable_width;
276 	}
277 
278 	size_t	numSubDir = d->subDir.size();
279 	if (numSubDir == 0) {			// No more subdir
280 		return;				// Exit
281 	}
282 
283 	size_t	curSubDir = 0;
284 	for (sptr_list<DirectoryEntry>::iterator iter = d->subDir.begin();
285 	     iter != d->subDir.end(); ++iter, ++curSubDir) {
286 
287 		if (curSubDir == 0) {
288 			    			// First subdir entry
289 			    			// Draw a bar connected to
290 			    			// parent dir
291 			html.DrawEntityChar(pad, EID_BOXH);
292 			curCol++;
293 
294 			if (numSubDir == 1) {
295 			    			// Only one subdir present
296 			    			// Draw a bar
297 				html.DrawEntityChar(pad, EID_BOXH);
298 			    	curCol++;
299 			}
300 			else {
301 				html.DrawEntityChar(pad, EID_BOXHD);
302 			    	curCol++;
303 
304 				barStack.push_back(pos+1);
305 			}
306 			html.DrawEntityChar(pad, EID_BOXH);
307 			curCol++;
308 		}
309 		else if (curSubDir == numSubDir-1) {
310 			    			// Last subdir entry
311 			html.DrawChar(pad, ' ');
312 			html.DrawEntityChar(pad, EID_BOXUR);
313 			html.DrawEntityChar(pad, EID_BOXH);
314 			curCol += 3;
315 
316 			barStack.pop_back();
317 		}
318 		else {				// Middle entries
319 			html.DrawChar(pad, ' ');
320 			html.DrawEntityChar(pad, EID_BOXVR);
321 			html.DrawEntityChar(pad, EID_BOXH);
322 			curCol += 3;
323 		}
324 
325 		string new_dir = current_dir;
326 		if (new_dir.size() && new_dir[new_dir.size()-1] != '/')
327 			new_dir += '/';
328 		new_dir += (*iter)->GetNameStr();
329 
330 			    			// For links
331 		if ((*iter)->IsSymLink()) {
332 			GenerateSymLinkHTML(html, pad, (*iter), new_dir);
333 		}
334 		else {
335 			GenerateDirHTML(html, pad, (*iter), new_dir);
336 
337 				// Note: subdir. allowed even inside
338 				// unreadable or skipped dir
339 
340 			    			// Scan inside this dir
341 		    	RecursiveGenerateHTML(html, pad, *iter,
342 					      pos+3+GetStringWidth((*iter)->GetNameStr()),
343 					      new_dir);
344 	    	}
345 		if (curSubDir != numSubDir-1) {	// Not the last one
346 						// New line
347 			html.DrawChar(pad, '\n');
348 			NewLineUpdateRowCol();
349 						// Add vertical bars
350 			for (size_t i = 0, j = 0; i < pos; i++) {
351 				if (j < barStack.size() && barStack[j] == i) {
352 					html.DrawEntityChar(pad, EID_BOXV);
353 					curCol++;
354 
355 					j++;
356 				}
357 				else {
358 					html.DrawChar(pad, ' ');
359 					curCol++;
360 				}
361 			}
362 		}
363 	}
364 }
365 
DrawBegin(HyperDocument & html,WINDOW * pad)366 void	HyperTreeDraw::DrawBegin(HyperDocument &html, WINDOW *pad)
367 {
368 	htmlRow = 0;
369 	htmlCol = 0;
370 	curCol = 0;
371 
372 	if (! pad) {
373 		;			// Nothing to do
374 	}
375 	else {
376 		html.DrawBegin(pad);
377 
378 		time_t scanTime = GetScanTime();
379 		struct tm *scanTime2 = localtime(&scanTime);
380 
381 		gtstream bufstr;
382 		gtout(bufstr, _("%$ %$   Last scan: %$"))
383 		     << progName << version
384 		     << my_strftime(_("%b %e  %H:%M"), scanTime2);
385 		html.DrawSetTitle(bufstr.str());
386 	}
387 
388 }
389 
DrawEnd(HyperDocument & html,WINDOW * pad)390 void	HyperTreeDraw::DrawEnd(HyperDocument &html, WINDOW *pad)
391 {
392 	if (curCol != 0) {		// Not end with new line
393 		html.DrawChar(pad, '\n');
394 		NewLineUpdateRowCol();	// One more line needed
395 	}
396 
397 	if (! pad) {
398 //ofstream ofs("xx", ios::app);
399 //if (!pad)
400 //ofs << htmlRow << ' ' << htmlCol << ' ' << '\n';
401 		html.DrawSetSize(htmlRow, htmlCol);
402 	}
403 	else {
404 		;			// Nothing to do
405 	}
406 }
407 
Draw(HyperDocument & html,WINDOW * pad)408 void	HyperTreeDraw::Draw(HyperDocument &html, WINDOW *pad)
409 {
410 	DrawBegin(html, pad);
411 
412 	bool	first = true;
413 
414 	for (sptr_list<DirectoryEntry>::iterator iter = dirTree->begin();
415 	     iter != dirTree->end(); ++iter) {
416 
417 		if (!first) {		// New line need ?
418 			html.DrawChar(pad, '\n');
419 			NewLineUpdateRowCol();
420 		}
421 		else
422 			first = false;
423 
424 		string new_dir = (*iter)->GetNameStr();
425 
426 		if ((*iter)->IsSymLink()) {		// Symbolic link
427 			GenerateSymLinkHTML(html, pad, (*iter), new_dir);
428 		}
429 		else {
430 			GenerateDirHTML(html, pad, (*iter), new_dir);
431 
432 			curCol = GetStringWidth(new_dir);
433 
434 				// Note: subdir. allowed even inside
435 				// unreadable or skipped dir
436 						// Recursion...
437 			RecursiveGenerateHTML(html, pad, *iter, curCol, new_dir);
438 		}
439 	}
440 
441 	DrawEnd(html, pad);
442 }
443 
444 /*************************************************************************
445 	Match Objects
446 *************************************************************************/
447 
448 struct MatchInfo {
449 	string			name;
450 	sptr<DirectoryEntry>	dir;
451 	bool			is_curdir;
452 	unsigned		error;
453 
MatchInfoMatchInfo454 	MatchInfo(const string &name_, sptr<DirectoryEntry> &dir_,
455 		  bool is_curdir_, unsigned error_ = 0) :
456 		name(name_), dir(dir_), is_curdir(is_curdir_), error(error_)
457 	{
458 	}
459 };
460 
461 struct MatchInfoIsCurDir {
operator ()MatchInfoIsCurDir462 	bool operator()(MatchInfo &m) const {
463 		return m.is_curdir;
464 	}
465 };
466 
467 struct MatchInfoIsBookmarked {
operator ()MatchInfoIsBookmarked468 	bool operator()(MatchInfo &m) const {
469 		return m.dir->bookmarked;
470 	}
471 };
472 
473 struct MatchInfoIsNotBookmarkedNorCurDir {
474 	private:
475 		string curdir;
476 	public:
MatchInfoIsNotBookmarkedNorCurDirMatchInfoIsNotBookmarkedNorCurDir477 		MatchInfoIsNotBookmarkedNorCurDir(const string &s) : curdir(s) {
478 		}
operator ()MatchInfoIsNotBookmarkedNorCurDir479 		bool operator()(MatchInfo &m) const {
480 			return !m.dir->bookmarked && !m.is_curdir
481 				&& m.name != curdir;
482 		}
483 };
484 
485 /*************************************************************************
486 	Find Match
487 *************************************************************************/
488 
489 // The add_curdir feature isn't handled in fuzzy find because it
490 // doesn't work well with error sorting order.
FuzzyRecursiveFindMatchDir(list<MatchInfo> & match_list,sptr<DirectoryEntry> & dir,const string & str,regex_t * reg,const string & current_dir)491 void	FuzzyRecursiveFindMatchDir(list<MatchInfo> &match_list,
492 				   sptr<DirectoryEntry> &dir,
493 				   const string &str,
494 				   regex_t *reg,
495 				   const string &current_dir)
496 {
497 	unsigned subStrLen;
498 	unsigned StrLen;
499 	unsigned cnt1, cnt2, TotCnt;
500 	unsigned TmpErr;
501 	int TmpVal;
502 
503 	subStrLen = str.size();
504 	if (dir->IsDir() && dir->isUnreadable != 1) {
505 		string mstr = dir->GetNameStr();
506 		StrLen = mstr.size();
507 		for (size_t i = 0; i < StrLen; ++i)
508 			mstr[i] = tolower(mstr[i]);
509 
510 		if (subStrLen < StrLen) {
511 			TotCnt = StrLen-subStrLen+1;
512 			unsigned err = static_cast<unsigned>(-1);
513 			for (cnt1 = 0; cnt1 < TotCnt; cnt1++) {
514 				TmpErr = 0;
515 				for (cnt2 = 0; cnt2 < subStrLen; cnt2++) {
516 					TmpVal = mstr[cnt1+cnt2-1]
517 						 - tolower(str[cnt2]);
518 					if (TmpVal < 0)
519 						TmpVal =- TmpVal;
520 					TmpErr += TmpVal * (subStrLen-cnt2+1);
521 				}
522 				TmpErr += cnt1*2;
523 				TmpErr += StrLen-subStrLen;
524 				for (cnt2 = 0; cnt2 < subStrLen; cnt2++) {
525 					if (mstr.find(tolower(str[cnt2])) == mstr.npos)
526 						TmpErr += 100;
527 				}
528 
529 				if (TmpErr < err)
530 					err = TmpErr;
531 			}
532 
533 			if (match_list.size()
534 			    < static_cast<size_t>(kcdConfig.cfgFuzzySize)) {
535 				list<MatchInfo>::iterator iter = match_list.begin();
536 				for ( ; iter != match_list.end(); ++iter) {
537 					if ((*iter).error > err)
538 						break;
539 				}
540 //FIXME: Add regex matching here
541 					// Insert before iter
542 				match_list.insert(iter,
543 						  MatchInfo(current_dir, dir, false, err));
544 			}
545 					// We have a fully populated list
546 			else if (match_list.back().error > err) {
547 				match_list.pop_back();
548 
549 				list<MatchInfo>::iterator iter = match_list.begin();
550 				for ( ; iter != match_list.end(); ++iter) {
551 					if ((*iter).error > err)
552 						break;
553 				}
554 //FIXME: Add regex matching here
555 					// Insert before iter
556 				match_list.insert(iter,
557 						  MatchInfo(current_dir, dir, false, err));
558 			}
559 		}
560 	}
561 
562 	for (sptr_list<DirectoryEntry>::iterator iter = dir->subDir.begin();
563 	     iter != dir->subDir.end(); ++iter) {
564 
565 						// For dirs
566 		if ((*iter)->IsDir()) {
567 			string new_dir = current_dir;
568 			if (new_dir[new_dir.size()-1] != '/')
569 				new_dir += '/';
570 			new_dir += (*iter)->GetNameStr();
571 
572 			FuzzyRecursiveFindMatchDir(match_list, *iter,
573 						   str, reg, new_dir);
574 		}
575 	}
576 }
577 
FuzzyFindMatchDir(list<MatchInfo> & match_list,const string & str,regex_t * reg)578 size_t	FuzzyFindMatchDir(list<MatchInfo> &match_list, const string &str,
579 			  regex_t *reg)
580 {
581 	match_list.clear();
582 
583 	if (str.size() == 0)			// In case getcwd fails
584 		return 0;
585 
586 	for (sptr_list<DirectoryEntry>::iterator iter = dirTree.begin();
587 	     iter!=dirTree.end(); ++iter) {
588 		string new_dir = (*iter)->GetNameStr();
589 		FuzzyRecursiveFindMatchDir(match_list, *iter, str, reg,
590 					   new_dir);
591 	}
592 
593 	return match_list.size();
594 }
595 
RecursiveFindMatchDir(list<MatchInfo> & match_list,sptr<DirectoryEntry> & dir,const string & str,regex_t * reg,const string & current_dir,bool add_curdir)596 void	RecursiveFindMatchDir(list<MatchInfo> &match_list, sptr<DirectoryEntry> &dir,
597 			      const string &str, regex_t *reg,
598 			      const string &current_dir,
599 			      bool add_curdir)
600 {
601 	if (dir->IsDir() && dir->isUnreadable != 1 &&
602 	    dir->GetNameStr().find(str) != string::npos) {	// Found
603 
604 		if (MatchPathRegex(reg, current_dir))
605 			match_list.push_back(MatchInfo(current_dir, dir, false));
606 	}
607 	else if (add_curdir && saveCwd.size() && current_dir == saveCwd) {
608 							// Write current directory
609 							// as well
610 		match_list.push_back(MatchInfo(saveCwd, dir, true));
611 	}
612 
613 	for (sptr_list<DirectoryEntry>::iterator iter = dir->subDir.begin();
614 	     iter != dir->subDir.end(); ++iter) {
615 
616 			    			// For dirs
617 		if ((*iter)->IsDir()) {
618 			string new_dir = current_dir;
619 			if (new_dir[new_dir.size()-1] != '/')
620 				new_dir += '/';
621 			new_dir += (*iter)->GetNameStr();
622 
623 			RecursiveFindMatchDir(match_list, *iter, str, reg,
624 					      new_dir, add_curdir);
625 		}
626 	}
627 }
628 
FindMatchDir(list<MatchInfo> & match_list,const string & str,regex_t * reg,bool add_curdir=false)629 size_t	FindMatchDir(list<MatchInfo> &match_list, const string &str,
630 		     regex_t *reg, bool add_curdir = false)
631 {
632 	match_list.clear();
633 
634 	if (str.size() == 0)		// In case getcwd fails
635 		return 0;
636 
637 	for (sptr_list<DirectoryEntry>::iterator iter = dirTree.begin();
638 	     iter != dirTree.end(); ++iter) {
639 		string new_dir = (*iter)->GetNameStr();
640 		RecursiveFindMatchDir(match_list, *iter, str, reg, new_dir,
641 				      add_curdir);
642 	}
643 
644 	if (kcdConfig.cfgFuzzySize &&
645 	    (match_list.size() == 0
646 	     || (match_list.size() == 1 && match_list.front().is_curdir == true)))
647 		return FuzzyFindMatchDir(match_list, str, reg);
648 
649 	return match_list.size();
650 }
651 
652 /*************************************************************************
653 	Generate HTML-like codes from the match list
654 *************************************************************************/
655 
656 class HyperListDraw : public HyperDraw {
657 		size_t		numDigit, max_length;
658 		list<MatchInfo> &match_list;
659 	public:
660 		HyperListDraw(list<MatchInfo> &match_list_);
661 		void Draw(HyperDocument &html, WINDOW *pad);
662 };
663 
HyperListDraw(list<MatchInfo> & match_list_)664 HyperListDraw::HyperListDraw(list<MatchInfo> &match_list_) :
665 				match_list(match_list_)
666 {
667 	size_t	tempNumMatch = match_list.size();
668 	numDigit = 0;
669 	while (tempNumMatch) {
670 		tempNumMatch /= 10;
671 		numDigit++;
672 	}
673 
674 	max_length = 0;
675 	for (list<MatchInfo>::iterator iter = match_list.begin();
676 	     iter != match_list.end(); iter++) {
677 		string s = iter->name;
678 		ReplaceControlChar(s);
679 		size_t size = GetStringWidth(s);
680 		if (size > max_length)
681 			max_length = size;
682 	}
683 }
684 
Draw(HyperDocument & html,WINDOW * pad)685 void	HyperListDraw::Draw(HyperDocument &html, WINDOW *pad)
686 {
687 
688 	if (!pad) {
689 				// Determine document size
690 //		gtstream bufstr;
691 //		gtout(bufstr, "echo %$ %$ > xx") << match_list.size() << 3+numDigit+max_length;
692 //		system(bufstr.str());
693 
694 		html.DrawSetSize(match_list.size(), 3+numDigit+max_length);
695 	}
696 	else {
697 				// Draw document
698 		html.DrawBegin(pad);
699 
700 		string	s = "";
701 		for (size_t i = 0; i < numDigit + 2; ++i)
702 			s += ' ';
703 
704 		time_t scanTime = GetScanTime();
705 		struct tm *scanTime2 = localtime(&scanTime);
706 
707 		gtstream bufstr;
708 		gtout(bufstr, _("%$ %$   Found: %$   Last scan: %$"))
709 		     << progName << version << match_list.size()
710 		     << my_strftime(_("%b %e  %H:%M"), scanTime2);
711 		html.DrawSetTitle(bufstr.str());
712 
713 		size_t	index = 1;
714 		for (list<MatchInfo>::iterator iter = match_list.begin();
715 		     iter != match_list.end(); iter++) {
716 
717 			if (index != 1) {		// New line need ?
718 				html.DrawChar(pad, '\n');
719 			}
720 
721 					// Write tags
722 			html.DrawAName(pad, iter->name);
723 			html.DrawACallBegin(pad, iter->name);
724 
725 			size_t index1 = index;
726 			for (size_t i = 0; i < numDigit && index1; ++i) {
727 				s[numDigit-i] = (index1 % 10) + '0';
728 				index1 /= 10;
729 			}
730 			html.DrawString(pad, s);
731 
732 			string ss = iter->name;
733 			ReplaceControlChar(ss);
734 			html.DrawString(pad, ss);
735 
736 			html.DrawChar(pad, ' ');
737 
738 			for (size_t i = 0; i < max_length-GetStringWidth(iter->name); i++) {
739 				html.DrawChar(pad, ' ');
740 			}
741 
742 		    			// End <A CALL=...> tag
743 			html.DrawAEnd(pad);
744 
745 			index++;
746 		}
747 	}
748 }
749 
750 /*************************************************************************
751 	Add current working directory to dirTree
752 	Size effect: chdir() called if dir not found
753 *************************************************************************/
754 
ExtendTree(sptr<DirectoryEntry> & d,const string & str)755 void	ExtendTree(sptr<DirectoryEntry> &d, const string &str)
756 {
757 	string::size_type delim;
758 
759 	if ((delim = str.find('/')) != string::npos) {	// Contains subdir
760 							// Need to recurse
761 
762 						// Split str into 2 string
763 		string str_first(str, 0, delim);
764 		string str_last(str, delim+1);
765 
766 		sptr<DirectoryEntry> d2(new DirectoryEntry(str_first));
767 		d2->isSkip = 2;			// Mark as [*]
768 
769 		d->subDir.push_back(d2);	// Add our own
770 
771 		if (kcdConfig.cfgSortTree)
772 			d->subDir.sort();
773 
774 		if (str_last.size()) {
775 			k_chdir(str_first);	// Require recursion
776 			ExtendTree(d2, str_last);
777 		}
778 	}
779 	else {					// No more subdir
780 		sptr<DirectoryEntry> d2(new DirectoryEntry(str));
781 		d2->isSkip = 2;			// Mark as [*]
782 
783 		d->subDir.push_back(d2);	// Add our own
784 
785 		if (kcdConfig.cfgSortTree)
786 			d->subDir.sort();
787 	}
788 }
789 
790 // change - when true, check honor_cfg if change is required
791 // honor_cfg - when true, don't modify tree if directory is in the list of SkipDir
792 
RecursiveFindDir(sptr<DirectoryEntry> & d,const string & current_dir,const string & str,bool honor_cfg,bool change)793 FindDirInfo *RecursiveFindDir(sptr<DirectoryEntry> &d, const string &current_dir,
794 			      const string &str, bool honor_cfg, bool change)
795 {
796 			// Check of subdir. of d
797 	for (sptr_list<DirectoryEntry>::iterator iter = d->subDir.begin();
798 	     iter != d->subDir.end(); ++iter) {
799 
800 		if ((*iter)->IsDir()) {		// Make sure it's not a
801 						// symbolic link
802 
803 			string new_dir = current_dir;
804 
805 						// Last char is not `/'
806 			if (new_dir[new_dir.size()-1] != '/') {
807 				new_dir += '/';		// Add one
808 			}
809 			new_dir += (*iter)->GetNameStr();	// Add dir. name
810 
811 					// Name to long, impossible to match
812 					// against this dir.
813 			if (new_dir.size() > str.size())
814 				continue;	// Skip this
815 
816 					// Same length
817 					// Match possible
818 			if (new_dir.size() == str.size()) {
819 
820 				if (str != new_dir)
821 					continue;	// Not matched
822 							// Skip this
823 				return new FindDirInfo((*iter), str);	// Matched
824 			}
825 
826 				// Now new_dir.size() < str.size()
827 
828 				// Can match first part of dir.
829 			if (new_dir.size() != 1 && str[new_dir.size()] != '/')
830 				continue;
831 
832 
833 					// We're left with this case
834 					// str     -> /xxxx/yyyy
835 					// new_dir -> /xxxx
836 
837 					// This case does not happen here
838 					// (new_dir is not top-level)
839 					// str     -> /xxxx
840 					// new_dir -> /
841 
842 			if (str.substr(0, new_dir.size()) == new_dir) {
843 
844 				// Note: subdir. allowed even inside
845 				// unreadable or skipped dir
846 
847 						// Recursion...
848 				return RecursiveFindDir(*iter, new_dir,
849 							str, honor_cfg, change);
850 			}
851 		}
852 	}
853 
854 	if (change) {			// No match, new one added
855 					// if change == true
856 
857 		if (honor_cfg && FindSkipDir(current_dir))
858 			return 0;
859 
860 //		if (!d->isSkipped && kcdConfig.cfgAutoScan) {
861 //		}
862 
863 
864 		// No match, need to branch the current dir from here
865 
866 			// Two cases:
867 			// Need to check whether parent dir. is root
868 			// or not
869 		if (current_dir.size() == 1) {		// Branching from root dir
870 
871 			k_chdir("/");			// Required to obtain permission
872 							// in ExtendTree(...)
873 
874 							// Ignore the first `/'
875 			ExtendTree(d, str.substr(1));
876 		}
877 		else {			// str[current_dir.size()] == `/'
878 					// Branching from other dir
879 
880 			k_chdir(current_dir);		// Required to obtain permission
881 							// in ExtendTree(...)
882 
883 							// Ignore the `/'
884 			ExtendTree(d, str.substr(current_dir.size()+1));
885 		}
886 		return new FindDirInfo(d, current_dir);
887 	}
888 	else
889 		return 0;
890 }
891 
FindDir(sptr_list<DirectoryEntry> * tree,const string & str,bool honor_cfg,bool change_top,bool change_sub)892 FindDirInfo *FindDir(sptr_list<DirectoryEntry> *tree, const string &str,
893 		     bool honor_cfg, bool change_top, bool change_sub)
894 {
895 	if (str.size() == 0)
896 		return 0;
897 						// Look for top-level directories
898 
899 	for (sptr_list<DirectoryEntry>::iterator iter = tree->begin();
900 	     iter != tree->end(); ++iter) {
901 
902 		if ((*iter)->IsDir()) {		// Make sure it's not a
903 						// symbolic link
904 
905 			string new_dir = (*iter)->GetNameStr();
906 
907 					// Name to long, impossible to match
908 					// against this dir.
909 			if (new_dir.size() > str.size())
910 				continue;	// Skip this
911 
912 					// Same length
913 					// Match possible
914 			if (new_dir.size() == str.size()) {
915 
916 				if (str != new_dir)
917 					continue;	// Not matched
918 							// Skip this
919 				return new FindDirInfo(*iter, str);	// Matched
920 			}
921 
922 				// Now new_dir.size() < str.size()
923 
924 				// Can match first part of dir.
925 			if (new_dir.size() != 1 && str[new_dir.size()] != '/')
926 				continue;
927 
928 				// We're left with two cases
929 				// str     -> /xxxx		(first case)
930 				// new_dir -> /
931 				// str     -> /xxxx/yyyy	(second case)
932 				// new_dir -> /xxxx
933 
934 			if (str.substr(0, new_dir.size()) == new_dir) {
935 
936 				// Note: subdir. allowed even inside
937 				// unreadable or skipped dir
938 
939 						// Recursion...
940 				return RecursiveFindDir(*iter, new_dir,
941 							str, honor_cfg, change_sub);
942 			}
943 		}
944 	}
945 
946 				// Cannot find current dir. in top-level dir
947 	if (change_top) {
948 		if (honor_cfg && ! FindStartDir(str))
949 			return 0;
950 
951 		sptr<DirectoryEntry> d(new DirectoryEntry(str));
952 		d->isSkip = 2;		// Mark as [*]
953 		tree->push_back(d);	// Add our own top-level entry
954 
955 		if (kcdConfig.cfgSortTree)
956 			tree->sort();
957 
958 		return new FindDirInfo(d, str);
959 	}
960 	else
961 		return 0;
962 }
963 
FindDir(sptr_list<DirectoryEntry> * tree,const string & str,bool honor_cfg,bool change)964 FindDirInfo *FindDir(sptr_list<DirectoryEntry> *tree, const string &str,
965 		     bool honor_cfg, bool change)
966 {
967 	return FindDir(tree, str, honor_cfg, change, change);
968 }
969 
970 /*************************************************************************
971 	Screen setup
972 *************************************************************************/
973 
FullScreenMode(HyperDraw * drawObj,const string & hyperInitPos)974 void FullScreenMode(HyperDraw *drawObj, const string &hyperInitPos)
975 {
976 	HyperDocument		*html = new HyperDocument(drawObj);
977 
978 	{
979 						// Display
980 		NCurses::Init();
981 
982 		try {
983 
984 			if (COLS < 50 || LINES < 10) {
985 				throw ErrorScreenTooSmall();
986 			}
987 			else {
988 				init_entity_table();
989 
990 				khGeometryManager	geoMan(modeHVScroll);
991 				khScreenManager		scrnMan(geoMan);
992 
993 				khStatusWindowWithFind	findBar(scrnMan, idStatWindow, NULL);
994 				khURLWindowWithFind	urlBar(scrnMan, idURLWindow);
995 				khHScrollBar		hscroll(scrnMan, idHScrollBar);
996 				khVScrollBar		vscroll(scrnMan, idVScrollBar);
997 				khScrollBox		cursorRest(scrnMan, idScrollBox);
998 
999 				urlBar.SetTitle(" ");		// Empty title
1000 				scrnMan.SetCursor(&cursorRest);
1001 				scrnMan.RequestUpdateAndRestCursor();
1002 
1003 							// Use pre-computed
1004 							// document size
1005 				khHyperWindowWithFind	hyper(scrnMan, idHyperWindow,
1006 							      *html, findBar, urlBar,
1007 							      hscroll, vscroll, hyperInitPos,
1008 							      1, htmlRow, htmlCol);
1009 
1010 				findBar.SetFindWindow(&hyper);
1011 				hscroll.SetWin(&hyper);	// Handle scroll through mouse
1012 				vscroll.SetWin(&hyper);
1013 
1014 							// Set cfgSpaceSelect flag
1015 				khStatusWindowWithFind::spaceSelect = kcdConfig.cfgSpaceSelect;
1016 
1017 							// Send all keyboard input
1018 							// to hyper
1019 				scrnMan.SetFocus(&hyper);
1020 
1021 							// Paint screen
1022 				scrnMan.RequestUpdateAndRestCursor();
1023 				hyper.KeyboardLoop();
1024 			}
1025 		}
1026 		catch (...) {
1027 			move(LINES-1, 0);
1028 			printf("\n\n");		// Avoid printing error
1029 						// message on top of drawn
1030 						// screen
1031 			NCurses::End();
1032 			throw;
1033 		}
1034 		NCurses::End();
1035 	}
1036 }
1037 
1038 /*************************************************************************
1039 	MountDir handling
1040 *************************************************************************/
1041 
ProcessMountDir(bool search,const string & root)1042 void	ProcessMountDir(bool search, const string &root)
1043 {
1044 	for (MountDirList::iterator iter = kcdConfig.cfgMountDir->begin();
1045 	     iter != kcdConfig.cfgMountDir->end(); ++iter) {
1046 		FindDirInfo *f = FindDir(&dirTree, (*iter)->dir, false);
1047 		if (f) {
1048 			f->dir_ptr->subDir.clear();
1049 
1050 			if (root.size()) {
1051 				if ((*iter)->dir.size() < root.size()
1052 				    || (*iter)->dir.substr(0, root.size())
1053 					!= root)
1054 					continue;
1055 			}
1056 
1057 			f->dir_ptr->isSkip = 0;
1058 
1059 				// This make sure the MountDir
1060 				// directory is actually scanned
1061 				// and not skipped
1062 			MountDirList* m = kcdConfig.cfgMountDir;
1063 			kcdConfig.cfgMountDir = new MountDirList;
1064 
1065 			const MountActionOrderType &mountAction = (*iter)->mountAction;
1066 
1067 			for (MountActionOrderType::const_iterator iter2 = mountAction.begin();
1068 			     iter2 != mountAction.end(); ++iter2) {
1069 
1070 				bool result = false;
1071 				switch (*iter2) {
1072 					case mountActionSkip:
1073 						result = true;
1074 						break;
1075 					case mountActionAll:
1076 						MountDirScanDir((*iter)->dir);
1077 						result = true;
1078 						break;
1079 					case mountActionTree:
1080 						if (search)
1081 							;
1082 						else {
1083 							MountDirScanDir((*iter)->dir);
1084 							result = true;
1085 						}
1086 						break;
1087 					case mountActionFileList:
1088 						break;
1089 				}
1090 
1091 				if (result)
1092 					break;
1093 			}
1094 
1095 			delete kcdConfig.cfgMountDir;
1096 			kcdConfig.cfgMountDir = m;
1097 		}
1098 		delete f;
1099 	}
1100 }
1101 
1102 /*************************************************************************
1103 	Display tree functions
1104 *************************************************************************/
1105 
AddDisplayTree(const string & root,sptr_list<DirectoryEntry> * tree)1106 void AddDisplayTree(const string &root, sptr_list<DirectoryEntry> *tree)
1107 {
1108 	string root_abs = RelativeToAbsolutePath(root, saveCwd);
1109 	root_abs = ExpandDirectory(root_abs, false);
1110 
1111 						// Allocate from heap since
1112 						// its lifetime is longer than
1113 						// this function
1114 	DirectoryEntry *entry = new DirectoryEntry(root_abs);
1115 	FindDirInfo *find = FindDir(&dirTree, root_abs, false, false);
1116 
1117 	if (find)
1118 		entry->subDir = find->dir_ptr->subDir;
1119 	else
1120 		entry->isSkip = 2;		// Not in file
1121 
1122 	tree->push_back(entry);
1123 }
1124 
GetDisplayTree(const string & root)1125 HyperDraw *GetDisplayTree(const string &root)
1126 {
1127 							// Allocate from heap since
1128 							// its lifetime is longer than
1129 							// this function
1130 	sptr_list<DirectoryEntry> *tree = new sptr_list<DirectoryEntry>;
1131 
1132 	if (root.size()) {
1133 		AddDisplayTree(root, tree);
1134 							// Add current directory after
1135 							// adding the entry above
1136 		if (saveCwd.size())			// Current directory is available
1137 			FindDir(tree, saveCwd, false);
1138 	}
1139 	else if (saveCwd.size()) {
1140 							// Allocate from heap since
1141 							// its lifetime is longer than
1142 							// this function
1143 		DirectoryEntry *entry = new DirectoryEntry(saveCwd);
1144 		FindDirInfo *find = FindDir(&dirTree, saveCwd, false, false);
1145 		if (find)
1146 			entry->subDir = find->dir_ptr->subDir;
1147 		else
1148 			entry->isSkip = 2;		// Not in file
1149 		tree->push_back(entry);
1150 	}
1151 	else
1152 		throw ErrorGenericCommandLine(_("cannot determine current directory"));
1153 
1154 	HyperDraw *drawObj;
1155 	if (tree->size()) {
1156 		RecursiveResolveSymLink(tree, *tree, string(""));
1157 		drawObj = new HyperTreeDraw(tree);
1158 	}
1159 	else						// Display entire tree
1160 		drawObj = new HyperTreeDraw(&dirTree);
1161 	return drawObj;
1162 }
1163 
DisplayTree(const string & root,bool use_mount_dir,bool rescan)1164 void	DisplayTree(const string &root, bool use_mount_dir, bool rescan)
1165 {
1166 	string str;
1167 	if (root.size() == 0) {
1168 		if (saveCwd.size())
1169 			str = saveCwd;
1170 		else
1171 			throw ErrorGenericCommandLine(_("cannot determine current directory"));
1172 	}
1173 	else {
1174 		str = RelativeToAbsolutePath(root, saveCwd);
1175 		str = ExpandDirectory(str, false);
1176 	}
1177 
1178 	FindDirInfo *find = FindDir(&dirTree, str, false, false);
1179 	if (!find)
1180 		rescan = true;
1181 
1182 	if (rescan)
1183 		ScanDirAndDisplay(str, false);
1184 
1185 	if (use_mount_dir)
1186 		ProcessMountDir(false, root);
1187 
1188 	GenerateHTMLStrings();
1189 	HyperDraw *drawObj = GetDisplayTree(str);
1190 	FullScreenMode(drawObj, "");
1191 }
1192 
DisplayFullTree(bool use_mount_dir,bool rescan)1193 void	DisplayFullTree(bool use_mount_dir, bool rescan)
1194 {
1195 	if (rescan)
1196 		ScanDir(false);
1197 
1198 	if (use_mount_dir)
1199 		ProcessMountDir(false, "");
1200 
1201 	GenerateHTMLStrings();
1202 	HyperDraw *drawObj = new HyperTreeDraw(&dirTree);
1203 	FullScreenMode(drawObj, "");
1204 }
1205 
1206 /*************************************************************************
1207 	Change directory
1208 *************************************************************************/
1209 
1210 void	GetBookmarks(list<MatchInfo> &match_list, const string &str,
1211 		     regex_t *reg);
1212 
CheckCDString(const string & str)1213 void	CheckCDString(const string &str)
1214 {
1215 	if (str.size()) {
1216 #ifdef	CLIB_HAVE_REGEX
1217 		if (str[0] == '/')
1218 			throw ErrorGenericCommandLine(_("invalid search string"));
1219 		if (str[str.size()-1] == '/')
1220 			throw ErrorGenericCommandLine(_("invalid search string"));
1221 		if (str.find("///") != string::npos)
1222 			throw ErrorGenericCommandLine(_("invalid search string"));
1223 #else
1224 		if (str.find('/'))
1225 			throw ErrorGenericCommandLine(_("regular expression not supported"));
1226 #endif
1227 	}
1228 }
1229 
1230 // FIXME: init_pos and adjust look redundant
1231 
DirListCD(list<MatchInfo> & match_list,const string & init_pos,const string & str,size_t adjust,size_t match_no)1232 void	DirListCD(list<MatchInfo> &match_list, const string &init_pos,
1233 		  const string &str, size_t adjust, size_t match_no)
1234 {
1235 	list<MatchInfo>::iterator iter;
1236 
1237 	if (match_no) {
1238 							// Current directory is not
1239 							// need here.  Remove it.
1240 		match_list.remove_if(MatchInfoIsCurDir());
1241 		adjust = 0;
1242 
1243 		iter = match_list.begin();
1244 							// match_no starts at 1
1245 		for (size_t i = 1; i < match_no; ++i, ++iter)
1246 			;
1247 	}
1248 	else {
1249 		list<MatchInfo>::iterator current;
1250 
1251 		for (iter = match_list.begin();
1252 		     iter != match_list.end(); ++iter) {
1253 			if ((*iter).is_curdir || (*iter).name == init_pos) {
1254 				current = iter;
1255 				break;
1256 			}
1257 		}
1258 		if (iter == match_list.end()) {
1259 							// getcwd fails
1260 
1261 							// This checks every entry
1262 							// in the list
1263 
1264 			iter = match_list.begin();	// Arbitrary pick the first
1265 							// one
1266 			current = iter;			// Set terminate condition
1267 							// for the while loop below
1268 		}
1269 		else {
1270 							// This checks every entry
1271 							// except the current dir
1272 
1273 			iter = current;
1274 			next_loop(match_list, iter);	// Start from the next one
1275 		}
1276 
1277 							// Loop until we find a
1278 							// directory that actually
1279 							// exists
1280 		while (k_access((*iter).name, F_OK)) {
1281 			cout << flush;			// Avoid out-of-order display
1282 			gtout(cerr, _("%$: skipping %$"
1283 				      " - directory no longer exist\n"))
1284 			     << progName << (*iter).name;
1285 
1286 			next_loop(match_list, iter);
1287 			if (iter == current) {
1288 							// Can reach here when we
1289 							// have matches but all
1290 							// no longer exist
1291 				if (!(*current).is_curdir) {
1292 					cout << _("no directory change\n");
1293 					return;
1294 				}
1295 				else
1296 					throw ErrorGenericFile(_("cannot find directory "
1297 								 "matching the string %$"),
1298 							       str);
1299 			}
1300 		}
1301 	}
1302 
1303 	if ((*iter).is_curdir || (*iter).name == init_pos) {
1304 		cout << _("no directory change\n");
1305 		return;
1306 	}
1307 
1308 	FILE	*file = k_fopen(dirFile, "wb");
1309 	if (file == NULL) {			// Cannot create or truncate
1310 						// ~/.kcd.newdir
1311 		throw ErrorGenericFile(_("cannot create file %$"),
1312 				       dirFile);
1313 	}
1314 
1315 							// Output desired dir
1316 	k_fputs((*iter).name, file);
1317 	fclose(file);
1318 
1319 	bool show_new = false;
1320 	switch (kcdConfig.cfgShowNewDir) {
1321 		case showNewDirNo:
1322 			break;
1323 		case showNewDirYes:
1324 			show_new = true;
1325 			break;
1326 		case showNewDirMulti:
1327 							// More than one matched dir
1328 			if (match_list.size() > adjust+1)
1329 				show_new = true;
1330 			break;
1331 	}
1332 	if (show_new)
1333 		cout << _("change to ") << (*iter).name << '\n';
1334 }
1335 
CD(const string & linktext,bool entire_tree,bool use_mount_dir,size_t match_no)1336 void	CD(const string &linktext, bool entire_tree,
1337 	   bool use_mount_dir, size_t match_no)
1338 {
1339 	CheckCDString(linktext);
1340 
1341 	LoadDirFile(true);
1342 
1343 	if (use_mount_dir)
1344 		ProcessMountDir(linktext.size(), "");
1345 
1346 	HyperDraw *drawObj = 0;
1347 	list<MatchInfo>	match_list;			// Must match lifetime
1348 							// of drawObj!
1349 
1350 	if (saveCwd.size())				// Current directory is available
1351 		FindDir(&dirTree, saveCwd, false);
1352 
1353 	string	hyperInitPos;
1354 
1355 	GenerateHTMLStrings();
1356 
1357 	if (linktext.size()) {				// kcd DIR
1358 		string	str = MakeString(linktext);
1359 		regex_t *reg = MakePathRegex(linktext);
1360 
1361 		ImportBookmarks("");
1362 
1363 		if (entire_tree)			// Ignore bookmarks
1364 							// We add current
1365 							// directory into the mix
1366 							// to aid cycling through
1367 							// all match directory
1368 							// in the command line
1369 			FindMatchDir(match_list, str, reg, true);
1370 
1371 		else {
1372 			GetBookmarks(match_list, str, reg);
1373 			if (match_list.size()) {
1374 				if (match_list.size() > 1 && saveCwd.size()) {
1375 					list<MatchInfo>::iterator iter;
1376 					for (iter = match_list.begin();
1377 					     iter != match_list.end(); ++iter) {
1378 						if ((*iter).name == saveCwd)
1379 							break;
1380 					}
1381 					if (iter == match_list.end()) {
1382 							// Add current directory to aid jumping
1383 						FindDirInfo *f = FindDir(&dirTree, saveCwd, true, false);
1384 						match_list.push_front(MatchInfo(saveCwd, f->dir_ptr, true));
1385 					}
1386 				}
1387 			}
1388 			else {
1389 				FindMatchDir(match_list, str, reg, true);
1390 			}
1391 		}
1392 
1393 		size_t	adjust = 0;			// In case getcwd fails
1394 		for (list<MatchInfo>::iterator iter = match_list.begin();
1395 		     iter != match_list.end(); ++iter) {
1396 			if ((*iter).is_curdir) {
1397 				adjust = 1;
1398 				break;
1399 			}
1400 		}
1401 							// +1 because of current
1402 							// directory in match_list
1403 		if ((match_list.size() > static_cast<size_t>(kcdConfig.cfgShowListThreshold)
1404 					 +adjust
1405 		     && match_no == 0)
1406 		    || match_no+adjust > match_list.size()) {
1407 
1408 							// Show whole list
1409 
1410 							// Current directory is not
1411 							// need here.  Remove it.
1412 			match_list.remove_if(MatchInfoIsCurDir());
1413 			drawObj = new HyperListDraw(match_list);
1414 
1415 			hyperInitPos = "";
1416 
1417 		}
1418 		else {					// Jump immediately
1419 			DirListCD(match_list, saveCwd, linktext, adjust, match_no);
1420 			return;
1421 		}
1422 	}
1423 	else {						// Browse dir tree
1424 		if (kcdConfig.cfgDefaultTree->size()) {
1425 							// Allocate from heap since
1426 							// its lifetime is longer than
1427 							// this function
1428 			sptr_list<DirectoryEntry> *tree = new sptr_list<DirectoryEntry>;
1429 
1430 			for (DirList::iterator iter = kcdConfig.cfgDefaultTree->begin();
1431 			     iter != kcdConfig.cfgDefaultTree->end(); ++iter) {
1432 				AddDisplayTree((*iter)->dir, tree);
1433 			}
1434 							// Add current directory after
1435 							// adding the entry above
1436 			if (saveCwd.size())		// Current directory is available
1437 				FindDir(tree, saveCwd, false);
1438 			drawObj = new HyperTreeDraw(tree);
1439 		}
1440 		else
1441 			drawObj = new HyperTreeDraw(&dirTree);
1442 		hyperInitPos = saveCwd;
1443 	}
1444 
1445 #ifdef DUMP_HTML
1446 	{
1447 		Buffer	buffer;
1448 		int handle = open("/home/lerdsuwa/devel/kcd/debug", O_RDWR|O_CREAT, 0700);
1449 		write(handle, buffer.GetPtr(), strlen(buffer.GetPtr()));
1450 		close(handle);
1451 	}
1452 #endif
1453 
1454 	FullScreenMode(drawObj, hyperInitPos);
1455 }
1456 
1457 /*************************************************************************
1458 	Bookmark helper functions
1459 *************************************************************************/
1460 
1461 list<string> bookmark_list;
1462 
AddBookmark(const string & str,bool verify=true)1463 void	AddBookmark(const string &str, bool verify = true)
1464 {
1465 	string s = ExpandDirectory(str, false);
1466 	if (verify) {
1467 		FindDirInfo *f = FindDir(&dirTree, s, true, false);
1468 		if (f) {
1469 							// Prevent duplicate entries
1470 			if (find(bookmark_list.begin(), bookmark_list.end(), s)
1471 			    == bookmark_list.end())
1472 				bookmark_list.push_back(s);
1473 			else {
1474 				cout << flush;		// Avoid out-of-order display
1475 				gtout(cerr, _("%$: directory %$ already bookmarked\n"))
1476 				     << progName << s;
1477 			}
1478 		}
1479 		else {
1480 			cout << flush;			// Avoid out-of-order display
1481 			gtout(cerr, _("%$: cannot find %$ in saved directory tree\n"))
1482 			     << progName << s;
1483 		}
1484 		delete f;
1485 	}
1486 	else
1487 		bookmark_list.push_back(s);
1488 }
1489 
RemoveBookmark(const string & str,bool verify=true)1490 void	RemoveBookmark(const string &str, bool verify = true)
1491 {
1492 	string s = ExpandDirectory(str, false);
1493 	if (verify) {
1494 		FindDirInfo *f = FindDir(&dirTree, s, true, false);
1495 		if (!f) {
1496 			cout << flush;			// Avoid out-of-order display
1497 			gtout(cerr, _("%$: cannot find %$ in saved directory tree\n"
1498 				      "        Directory ignored\n"))
1499 			     << progName << s;
1500 		}
1501 		delete f;
1502 	}
1503 	remove(bookmark_list.begin(), bookmark_list.end(), s);
1504 }
1505 
GetBookmarks(list<MatchInfo> & match_list,const string & str,regex_t * reg)1506 void	GetBookmarks(list<MatchInfo> &match_list, const string &str,
1507 		     regex_t *reg)
1508 {
1509 	for (list<string>::const_iterator iter = bookmark_list.begin();
1510 	     iter != bookmark_list.end(); iter++) {
1511 
1512 		if (str.size()) {
1513 			if (MatchStr(str, *iter)
1514 			    && MatchPathRegex(reg, *iter)) {
1515 				FindDirInfo *f = FindDir(&dirTree, *iter, true, false);
1516 				if (f)
1517 					match_list.push_back(MatchInfo(*iter, f->dir_ptr, false));
1518 				delete f;
1519 			}
1520 		}
1521 		else {
1522 			FindDirInfo *f = FindDir(&dirTree, *iter, true, false);
1523 			if (f)
1524 				match_list.push_back(MatchInfo(*iter, f->dir_ptr, false));
1525 			delete f;
1526 		}
1527 	}
1528 }
1529 
OutputBookmarks(const string & filename)1530 void	OutputBookmarks(const string &filename)
1531 {
1532 	const char *	s;
1533 	if (filename.size())
1534 		s = filename.c_str();
1535 	else
1536 		s = bookmarkFile.c_str();
1537 
1538 	ofstream ofs(s);
1539 	if (!ofs) {
1540 		throw ErrorGenericFile(_("cannot create file %$"), s);
1541 	}
1542 
1543 	for (list<string>::const_iterator iter = bookmark_list.begin();
1544 	     iter != bookmark_list.end(); iter++) {
1545 		ofs << QuoteShellChars(*iter) << '\n';
1546 		if (!ofs)
1547 			throw ErrorGenericFile(_("error writing file %$"), s);
1548 	}
1549 }
1550 
1551 /*************************************************************************
1552 	Bookmark functions
1553 *************************************************************************/
1554 
ImportBookmarks(const string & filename)1555 void	ImportBookmarks(const string &filename)
1556 {
1557 	bookmark_list.clear();
1558 
1559 	const char *	s;
1560 	if (filename.size())
1561 		s = filename.c_str();
1562 	else
1563 		s = bookmarkFile.c_str();
1564 
1565 	ifstream ifs(s);
1566 	if (!ifs)		// Don't throw exception here
1567 		return;
1568 
1569 	for ( ; ; ) {
1570 		string	buffer;
1571 		getline(ifs, buffer);
1572 		if (ifs.eof())
1573 			break;
1574 		if (!ifs)
1575 			throw ErrorGenericFile(_("error reading file %$"), s);
1576 		try {
1577 			AddBookmark(UnquoteShellChars(buffer), false);
1578 		}
1579 		catch (ErrorRange &) {
1580 			// Continue next line without abort
1581 			// FIXME: Detect UnquoteShellChars errors
1582 		}
1583 	}
1584 }
1585 
ExportBookmarks(const string & filename)1586 void	ExportBookmarks(const string &filename)
1587 {
1588 	ImportBookmarks("");
1589 	OutputBookmarks(filename);
1590 }
1591 
ShowBookmarks(const string & linktext)1592 void	ShowBookmarks(const string &linktext)
1593 {
1594 	LoadDirFile(true);
1595 	ImportBookmarks("");
1596 
1597 	list<MatchInfo>	match_list;
1598 
1599 	if (linktext.size()) {				// kcd DIR
1600 		string	str = MakeString(linktext);
1601 		regex_t *reg = MakePathRegex(linktext);
1602 		GetBookmarks(match_list, str, reg);
1603 	}
1604 	else
1605 		GetBookmarks(match_list, linktext, 0);
1606 
1607 	if (!match_list.size()) {
1608 		cout << flush;			// Avoid out-of-order display
1609 		gtout(cerr, _("%$: no bookmarks found\n"))
1610 		     << progName;
1611 		return;
1612 	}
1613 
1614 	GenerateHTMLStrings();
1615 
1616 	HyperDraw *drawObj = new HyperListDraw(match_list);
1617 
1618 	FullScreenMode(drawObj, "");
1619 }
1620 
AddBookmarks(int argc,char * argv[],int argcBegin)1621 void	AddBookmarks(int argc, char *argv[], int argcBegin)
1622 {
1623 	LoadDirFile(true);
1624 	ImportBookmarks("");
1625 
1626 	if (argcBegin == argc) {
1627 		if (saveCwd.size())
1628 			AddBookmark(saveCwd, true);
1629 		else {
1630 			cout << flush;			// Avoid out-of-order display
1631 			gtout(cerr, _("%$: cannot determine current directory\n"
1632 				      "        Directory ignored"))
1633 			     << progName;
1634 			return;
1635 		}
1636 	}
1637 	else {
1638 		for (int i = argcBegin; i < argc; ++i) {
1639 			if (!argv[i][0])
1640 				continue;
1641 
1642 			if (argv[i][0] != '/' && argv[i][0] != '~'
1643 			    && !saveCwd.size()) {
1644 				cout << flush;			// Avoid out-of-order display
1645 				gtout(cerr, _("%$: cannot determine current directory for path %$\n"
1646 	 				      "        Directory ignored"))
1647 				     << progName << argv[i];
1648 			}
1649 			else {
1650 				string dir = RelativeToAbsolutePath(argv[i], saveCwd);
1651 				AddBookmark(dir, true);
1652 			}
1653 		}
1654 	}
1655 
1656 	OutputBookmarks("");
1657 }
1658 
RemoveBookmarks(int argc,char * argv[],int argcBegin)1659 void	RemoveBookmarks(int argc, char *argv[], int argcBegin)
1660 {
1661 	LoadDirFile(true);
1662 	ImportBookmarks("");
1663 
1664 	if (argcBegin == argc) {
1665 		if (saveCwd.size())
1666 			RemoveBookmark(saveCwd, true);
1667 		else {
1668 			cout << flush;			// Avoid out-of-order display
1669 			gtout(cerr, _("%$: cannot determine current directory\n"
1670 				      "        Directory ignored"))
1671 			     << progName;
1672 			return;
1673 		}
1674 	}
1675 	else {
1676 		for (int i = argcBegin; i < argc; ++i) {
1677 			if (!argv[i][0])
1678 				continue;
1679 
1680 			if (argv[i][0] != '/' && argv[i][0] != '~'
1681 			    && !saveCwd.size()) {
1682 				cout << flush;			// Avoid out-of-order display
1683 				gtout(cerr, _("%$: cannot determine current directory for path %$\n"
1684 					      "        Directory ignored"))
1685 				     << progName << argv[i];
1686 			}
1687 			else {
1688 				string dir = RelativeToAbsolutePath(argv[i], saveCwd);
1689 				AddBookmark(dir, true);
1690 			}
1691 		}
1692 	}
1693 
1694 	OutputBookmarks("");
1695 }
1696