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