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 ¤t_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 ¤t_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 ¤t_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 ¤t_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 ¤t_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