1 /* ncdu - NCurses Disk Usage
2 
3   Copyright (c) 2007-2021 Yoran Heling
4 
5   Permission is hereby granted, free of charge, to any person obtaining
6   a copy of this software and associated documentation files (the
7   "Software"), to deal in the Software without restriction, including
8   without limitation the rights to use, copy, modify, merge, publish,
9   distribute, sublicense, and/or sell copies of the Software, and to
10   permit persons to whom the Software is furnished to do so, subject to
11   the following conditions:
12 
13   The above copyright notice and this permission notice shall be included
14   in all copies or substantial portions of the Software.
15 
16   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 
24 */
25 
26 #include "global.h"
27 
28 #include <string.h>
29 #include <stdlib.h>
30 #include <ncurses.h>
31 #include <time.h>
32 
33 
34 static int graph = 1, show_as = 0, info_show = 0, info_page = 0, info_start = 0, show_items = 0, show_mtime = 0;
35 static const char *message = NULL;
36 
37 
38 
browse_draw_info(struct dir * dr)39 static void browse_draw_info(struct dir *dr) {
40   struct dir *t;
41   struct dir_ext *e = dir_ext_ptr(dr);
42   char mbuf[46];
43   int i;
44 
45   nccreate(11, 60, "Item info");
46 
47   if(dr->hlnk) {
48     nctab(41, info_page == 0, 1, "Info");
49     nctab(50, info_page == 1, 2, "Links");
50   }
51 
52   switch(info_page) {
53   case 0:
54     attron(A_BOLD);
55     ncaddstr(2, 3, "Name:");
56     ncaddstr(3, 3, "Path:");
57     if(!e)
58       ncaddstr(4, 3, "Type:");
59     else {
60       ncaddstr(4, 3, "Mode:");
61       ncaddstr(4, 21, "UID:");
62       ncaddstr(4, 33, "GID:");
63       ncaddstr(5, 3, "Last modified:");
64     }
65     ncaddstr(6, 3, "   Disk usage:");
66     ncaddstr(7, 3, "Apparent size:");
67     attroff(A_BOLD);
68 
69     ncaddstr(2,  9, cropstr(dr->name, 49));
70     ncaddstr(3,  9, cropstr(getpath(dr->parent), 49));
71     ncaddstr(4,  9, dr->flags & FF_DIR ? "Directory" : dr->flags & FF_FILE ? "File" : "Other");
72 
73     if(e) {
74       ncaddstr(4, 9, fmtmode(e->mode));
75       ncprint(4, 26, "%d", e->uid);
76       ncprint(4, 38, "%d", e->gid);
77       time_t t = (time_t)e->mtime;
78       strftime(mbuf, sizeof(mbuf), "%Y-%m-%d %H:%M:%S %z", localtime(&t));
79       ncaddstr(5, 18, mbuf);
80     }
81 
82     ncmove(6, 18);
83     printsize(UIC_DEFAULT, dr->size);
84     addstrc(UIC_DEFAULT, " (");
85     addstrc(UIC_NUM, fullsize(dr->size));
86     addstrc(UIC_DEFAULT, " B)");
87 
88     ncmove(7, 18);
89     printsize(UIC_DEFAULT, dr->asize);
90     addstrc(UIC_DEFAULT, " (");
91     addstrc(UIC_NUM, fullsize(dr->asize));
92     addstrc(UIC_DEFAULT, " B)");
93     break;
94 
95   case 1:
96     for(i=0,t=dr->hlnk; t!=dr; t=t->hlnk,i++) {
97       if(info_start > i)
98         continue;
99       if(i-info_start > 5)
100         break;
101       ncaddstr(2+i-info_start, 3, cropstr(getpath(t), 54));
102     }
103     if(t!=dr)
104       ncaddstr(8, 25, "-- more --");
105     break;
106   }
107 
108   ncaddstr(9, 31, "Press ");
109   addchc(UIC_KEY, 'i');
110   addstrc(UIC_DEFAULT, " to hide this window");
111 }
112 
113 
browse_draw_flag(struct dir * n,int * x)114 static void browse_draw_flag(struct dir *n, int *x) {
115   addchc(n->flags & FF_BSEL ? UIC_FLAG_SEL : UIC_FLAG,
116       n == dirlist_parent ? ' ' :
117         n->flags & FF_EXL ? '<' :
118         n->flags & FF_ERR ? '!' :
119        n->flags & FF_SERR ? '.' :
120       n->flags & FF_OTHFS ? '>' :
121      n->flags & FF_KERNFS ? '^' :
122      n->flags & FF_FRMLNK ? 'F' :
123       n->flags & FF_HLNKC ? 'H' :
124      !(n->flags & FF_FILE
125     || n->flags & FF_DIR) ? '@' :
126         n->flags & FF_DIR
127         && n->sub == NULL ? 'e' :
128                             ' ');
129   *x += 2;
130 }
131 
132 
browse_draw_graph(struct dir * n,int * x)133 static void browse_draw_graph(struct dir *n, int *x) {
134   float pc = 0.0f;
135   int o, i, bar_size = wincols/7 > 10 ? wincols/7 : 10;
136   enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT;
137 
138   if(!graph)
139     return;
140 
141   *x += graph == 1 ? (bar_size + 3) : graph == 2 ? 9 : (bar_size + 10);
142   if(n == dirlist_parent)
143     return;
144 
145   addchc(c, '[');
146 
147   /* percentage (6 columns) */
148   if(graph == 2 || graph == 3) {
149     pc = (float)(show_as ? n->parent->asize : n->parent->size);
150     if(pc < 1)
151       pc = 1.0f;
152     uic_set(c == UIC_SEL ? UIC_NUM_SEL : UIC_NUM);
153     printw("%5.1f", ((float)(show_as ? n->asize : n->size) / pc) * 100.0f);
154     addchc(c, '%');
155   }
156 
157   if(graph == 3)
158     addch(' ');
159 
160   /* graph (10+ columns) */
161   if(graph == 1 || graph == 3) {
162     uic_set(c == UIC_SEL ? UIC_GRAPH_SEL : UIC_GRAPH);
163     o = (int)((float)bar_size*((float)(show_as ? n->asize : n->size) / (float)(show_as ? dirlist_maxa : dirlist_maxs)));
164     for(i=0; i<bar_size; i++)
165       addch(i < o ? '#' : ' ');
166   }
167 
168   addchc(c, ']');
169 }
170 
171 
browse_draw_items(struct dir * n,int * x)172 static void browse_draw_items(struct dir *n, int *x) {
173   enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT;
174   enum ui_coltype cn = c == UIC_SEL ? UIC_NUM_SEL : UIC_NUM;
175 
176   if(!show_items)
177     return;
178   *x += 7;
179 
180   if(!n->items)
181     return;
182   else if(n->items < 100*1000) {
183     uic_set(cn);
184     printw("%6s", fullsize(n->items));
185   } else if(n->items < 1000*1000) {
186     uic_set(cn);
187     printw("%5.1f", n->items / 1000.0);
188     addstrc(c, "k");
189   } else if(n->items < 1000*1000*1000) {
190     uic_set(cn);
191     printw("%5.1f", n->items / 1e6);
192     addstrc(c, "M");
193   } else {
194     addstrc(c, "  > ");
195     addstrc(cn, "1");
196     addchc(c, 'B');
197   }
198 }
199 
200 
browse_draw_mtime(struct dir * n,int * x)201 static void browse_draw_mtime(struct dir *n, int *x) {
202   enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT;
203   char mbuf[26];
204   struct dir_ext *e;
205   time_t t;
206 
207   if (n->flags & FF_EXT) {
208     e = dir_ext_ptr(n);
209   } else if (!strcmp(n->name, "..") && (n->parent->flags & FF_EXT)) {
210     e = dir_ext_ptr(n->parent);
211   } else {
212     snprintf(mbuf, sizeof(mbuf), "no mtime");
213     goto no_mtime;
214   }
215   t = (time_t)e->mtime;
216 
217   strftime(mbuf, sizeof(mbuf), "%Y-%m-%d %H:%M:%S %z", localtime(&t));
218   uic_set(c == UIC_SEL ? UIC_NUM_SEL : UIC_NUM);
219 no_mtime:
220   printw("%26s", mbuf);
221   *x += 27;
222 }
223 
224 
browse_draw_item(struct dir * n,int row)225 static void browse_draw_item(struct dir *n, int row) {
226   int x = 0;
227 
228   enum ui_coltype c = n->flags & FF_BSEL ? UIC_SEL : UIC_DEFAULT;
229   uic_set(c);
230   mvhline(row, 0, ' ', wincols);
231   move(row, 0);
232 
233   browse_draw_flag(n, &x);
234   move(row, x);
235 
236   if(n != dirlist_parent)
237     printsize(c, show_as ? n->asize : n->size);
238   x += 10;
239   move(row, x);
240 
241   browse_draw_graph(n, &x);
242   move(row, x);
243 
244   browse_draw_items(n, &x);
245   move(row, x);
246 
247   if (extended_info && show_mtime) {
248     browse_draw_mtime(n, &x);
249     move(row, x);
250   }
251 
252   if(n->flags & FF_DIR)
253     c = c == UIC_SEL ? UIC_DIR_SEL : UIC_DIR;
254   addchc(c, n->flags & FF_DIR ? '/' : ' ');
255   addstrc(c, cropstr(n->name, wincols-x-1));
256 }
257 
258 
browse_draw()259 void browse_draw() {
260   struct dir *t;
261   const char *tmp;
262   int selected = 0, i;
263 
264   erase();
265   t = dirlist_get(0);
266 
267   /* top line - basic info */
268   uic_set(UIC_HD);
269   mvhline(0, 0, ' ', wincols);
270   mvprintw(0,0,"%s %s ~ Use the arrow keys to navigate, press ", PACKAGE_NAME, PACKAGE_VERSION);
271   addchc(UIC_KEY_HD, '?');
272   addstrc(UIC_HD, " for help");
273   if(dir_import_active)
274     mvaddstr(0, wincols-10, "[imported]");
275   else if(read_only)
276     mvaddstr(0, wincols-11, "[read-only]");
277 
278   /* second line - the path */
279   mvhlinec(UIC_DEFAULT, 1, 0, '-', wincols);
280   if(dirlist_par) {
281     mvaddchc(UIC_DEFAULT, 1, 3, ' ');
282     tmp = getpath(dirlist_par);
283     mvaddstrc(UIC_DIR, 1, 4, cropstr(tmp, wincols-8));
284     mvaddchc(UIC_DEFAULT, 1, 4+((int)strlen(tmp) > wincols-8 ? wincols-8 : (int)strlen(tmp)), ' ');
285   }
286 
287   /* bottom line - stats */
288   uic_set(UIC_HD);
289   mvhline(winrows-1, 0, ' ', wincols);
290   if(t) {
291     if(!show_as) attron(A_BOLD);
292     mvaddstr(winrows-1, 0, " Total disk usage: ");
293     if(!show_as) attroff(A_BOLD);
294     printsize(UIC_HD, t->parent->size);
295     if(show_as) attron(A_BOLD);
296     addstrc(UIC_HD, "  Apparent size: ");
297     if(show_as) attroff(A_BOLD);
298     uic_set(UIC_NUM_HD);
299     printsize(UIC_HD, t->parent->asize);
300     addstrc(UIC_HD, "  Items: ");
301     uic_set(UIC_NUM_HD);
302     printw("%d", t->parent->items);
303   } else
304     mvaddstr(winrows-1, 0, " No items to display.");
305   uic_set(UIC_DEFAULT);
306 
307   /* nothing to display? stop here. */
308   if(!t)
309     return;
310 
311   /* get start position */
312   t = dirlist_top(0);
313 
314   /* print the list to the screen */
315   for(i=0; t && i<winrows-3; t=dirlist_next(t),i++) {
316     browse_draw_item(t, 2+i);
317     /* save the selected row number for later */
318     if(t->flags & FF_BSEL)
319       selected = i;
320   }
321 
322   /* draw message window */
323   if(message) {
324     nccreate(6, 60, "Message");
325     ncaddstr(2, 2, message);
326     ncaddstr(4, 34, "Press any key to continue");
327   }
328 
329   /* draw information window */
330   t = dirlist_get(0);
331   if(!message && info_show && t != dirlist_parent)
332     browse_draw_info(t);
333 
334   /* move cursor to selected row for accessibility */
335   move(selected+2, 0);
336 }
337 
338 
browse_key(int ch)339 int browse_key(int ch) {
340   struct dir *t, *sel;
341   int i, catch = 0;
342 
343   /* message window overwrites all keys */
344   if(message) {
345     message = NULL;
346     return 0;
347   }
348 
349   sel = dirlist_get(0);
350 
351   /* info window overwrites a few keys */
352   if(info_show && sel)
353     switch(ch) {
354     case '1':
355       info_page = 0;
356       break;
357     case '2':
358       if(sel->hlnk)
359         info_page = 1;
360       break;
361     case KEY_RIGHT:
362     case 'l':
363       if(sel->hlnk) {
364         info_page = 1;
365         catch++;
366       }
367       break;
368     case KEY_LEFT:
369     case 'h':
370       if(sel->hlnk) {
371         info_page = 0;
372         catch++;
373       }
374       break;
375     case KEY_UP:
376     case 'k':
377       if(sel->hlnk && info_page == 1) {
378         if(info_start > 0)
379           info_start--;
380         catch++;
381       }
382       break;
383     case KEY_DOWN:
384     case 'j':
385     case ' ':
386       if(sel->hlnk && info_page == 1) {
387         for(i=0,t=sel->hlnk; t!=sel; t=t->hlnk)
388           i++;
389         if(i > info_start+6)
390           info_start++;
391         catch++;
392       }
393       break;
394     }
395 
396   if(!catch)
397     switch(ch) {
398     /* selecting items */
399     case KEY_UP:
400     case 'k':
401       dirlist_select(dirlist_get(-1));
402       dirlist_top(-1);
403       info_start = 0;
404       break;
405     case KEY_DOWN:
406     case 'j':
407       dirlist_select(dirlist_get(1));
408       dirlist_top(1);
409       info_start = 0;
410       break;
411     case KEY_HOME:
412       dirlist_select(dirlist_next(NULL));
413       dirlist_top(2);
414       info_start = 0;
415       break;
416     case KEY_LL:
417     case KEY_END:
418       dirlist_select(dirlist_get(1<<30));
419       dirlist_top(1);
420       info_start = 0;
421       break;
422     case KEY_PPAGE:
423       dirlist_select(dirlist_get(-1*(winrows-3)));
424       dirlist_top(-1);
425       info_start = 0;
426       break;
427     case KEY_NPAGE:
428       dirlist_select(dirlist_get(winrows-3));
429       dirlist_top(1);
430       info_start = 0;
431       break;
432 
433     /* sorting items */
434     case 'n':
435       dirlist_set_sort(DL_COL_NAME, dirlist_sort_col == DL_COL_NAME ? !dirlist_sort_desc : 0, DL_NOCHANGE);
436       info_show = 0;
437       break;
438     case 's':
439       i = show_as ? DL_COL_ASIZE : DL_COL_SIZE;
440       dirlist_set_sort(i, dirlist_sort_col == i ? !dirlist_sort_desc : 1, DL_NOCHANGE);
441       info_show = 0;
442       break;
443     case 'C':
444       dirlist_set_sort(DL_COL_ITEMS, dirlist_sort_col == DL_COL_ITEMS ? !dirlist_sort_desc : 1, DL_NOCHANGE);
445       info_show = 0;
446       break;
447     case 'M':
448       if (extended_info) {
449         dirlist_set_sort(DL_COL_MTIME, dirlist_sort_col == DL_COL_MTIME ? !dirlist_sort_desc : 1, DL_NOCHANGE);
450         info_show = 0;
451       }
452       break;
453     case 'e':
454       dirlist_set_hidden(!dirlist_hidden);
455       info_show = 0;
456       break;
457     case 't':
458       dirlist_set_sort(DL_NOCHANGE, DL_NOCHANGE, !dirlist_sort_df);
459       info_show = 0;
460       break;
461     case 'a':
462       show_as = !show_as;
463       if(dirlist_sort_col == DL_COL_ASIZE || dirlist_sort_col == DL_COL_SIZE)
464         dirlist_set_sort(show_as ? DL_COL_ASIZE : DL_COL_SIZE, DL_NOCHANGE, DL_NOCHANGE);
465       info_show = 0;
466       break;
467 
468     /* browsing */
469     case 10:
470     case KEY_RIGHT:
471     case 'l':
472       if(sel != NULL && sel->flags & FF_DIR) {
473         dirlist_open(sel == dirlist_parent ? dirlist_par->parent : sel);
474         dirlist_top(-3);
475       }
476       info_show = 0;
477       break;
478     case KEY_LEFT:
479     case KEY_BACKSPACE:
480     case 'h':
481     case '<':
482       if(dirlist_par && dirlist_par->parent != NULL) {
483         dirlist_open(dirlist_par->parent);
484         dirlist_top(-3);
485       }
486       info_show = 0;
487       break;
488 
489     /* and other stuff */
490     case 'r':
491       if(dir_import_active) {
492         message = "Directory imported from file, won't refresh.";
493         break;
494       }
495       if(dirlist_par) {
496         dir_ui = 2;
497         dir_mem_init(dirlist_par);
498         dir_scan_init(getpath(dirlist_par));
499       }
500       info_show = 0;
501       break;
502     case 'q':
503       if(info_show)
504         info_show = 0;
505       else
506         if (confirm_quit)
507           quit_init();
508         else return 1;
509       break;
510     case 'g':
511       if(++graph > 3)
512         graph = 0;
513       info_show = 0;
514       break;
515     case 'c':
516       show_items = !show_items;
517       break;
518     case 'm':
519       if (extended_info)
520         show_mtime = !show_mtime;
521       break;
522     case 'i':
523       info_show = !info_show;
524       break;
525     case '?':
526       help_init();
527       info_show = 0;
528       break;
529     case 'd':
530       if(read_only >= 1 || dir_import_active) {
531         message = read_only >= 1
532           ? "File deletion disabled in read-only mode."
533           : "File deletion not available for imported directories.";
534         break;
535       }
536       if(sel == NULL || sel == dirlist_parent)
537         break;
538       info_show = 0;
539       if((t = dirlist_get(1)) == sel)
540         if((t = dirlist_get(-1)) == sel || t == dirlist_parent)
541           t = NULL;
542       delete_init(sel, t);
543       break;
544      case 'b':
545       if(read_only >= 2 || dir_import_active) {
546         message = read_only >= 2
547           ? "Shell feature disabled in read-only mode."
548           : "Shell feature not available for imported directories.";
549         break;
550       }
551       shell_init();
552       break;
553     }
554 
555   /* make sure the info_* options are correct */
556   sel = dirlist_get(0);
557   if(!info_show || sel == dirlist_parent)
558     info_show = info_page = info_start = 0;
559   else if(sel && !sel->hlnk)
560     info_page = info_start = 0;
561 
562   return 0;
563 }
564 
565 
browse_init(struct dir * par)566 void browse_init(struct dir *par) {
567   pstate = ST_BROWSE;
568   message = NULL;
569   dirlist_open(par);
570 }
571 
572