1 module unde.file_manager.draw_path.draw_path;
2 
3 import unde.global_state;
4 import unde.lib;
5 import unde.lsblk;
6 import unde.scan;
7 import unde.clickable;
8 import unde.file_manager.draw_path.draw_elements;
9 import unde.file_manager.change_rights;
10 import unde.path_mnt;
11 import unde.viewers.image_viewer.lib;
12 import unde.viewers.text_viewer.lib;
13 import unde.slash;
14 
15 import berkeleydb.all;
16 import core.thread;
17 
18 import derelict.sdl2.sdl;
19 
20 import std..string;
21 import std.format;
22 import std.conv;
23 import std.stdio;
24 import std.datetime;
25 import std.regex;
26 import std.process;
27 import std.utf;
28 
29 import core.stdc..string;
30 import core.stdc.errno;
31 
32 import std.file;
33 import core.sys.posix.sys.stat;
34 
35 version (Windows)
36 {
37 import unde.scan;
38 }
39 
40 private void
41 calculate_parent_animation_info(GlobalState gs, ref RectSize rectsize,
42         SortType sort,
43         ref SDL_Rect sdl_rect,
44         ref bool calculated,
45         ref CoordinatesPlusScale screen)
46 {
47     sdl_rect = rectsize.rect(sort).to_screen(screen);
48 
49     sdl_rect.x = sdl_rect.x+sdl_rect.w/3;
50     sdl_rect.y = sdl_rect.y+sdl_rect.h/3;
51     sdl_rect.w /= 3;
52     sdl_rect.h /= 3;
53 
54     calculated = true;
55 }
56 
57 private void
58 calculate_directory_animation_info(GlobalState gs, ref RectSize rectsize,
59         SortType sort,
60         ref SDL_Rect sdl_rect,
61         ref bool calculated,
62         ref CoordinatesPlusScale screen)
63 {
64     sdl_rect = rectsize.rect(sort).to_screen(screen);
65     calculated = true;
66 }
67 
68 private void
69 calculate_animation_info(GlobalState gs, ref RectSize rectsize,
70         string path,
71         SortType sort)
72 {
73     foreach (ref animation_info; gs.animation_info)
74     {
75         if ( animation_info.parent == path && !animation_info.from_calculated && 
76                 (animation_info.type == NameType.CreateDirectory ||
77                  (animation_info.type == NameType.Copy || 
78                   animation_info.type == NameType.Move) && animation_info.stage == 1) )
79         {
80             calculate_parent_animation_info(gs, rectsize,
81                     sort,
82                     animation_info.from,
83                     animation_info.from_calculated,
84                     gs.screen);
85         }
86         if ( animation_info.parent == path && !animation_info.to_calculated && 
87                 (animation_info.type == NameType.Copy ||
88                  animation_info.type == NameType.Move) &&
89                 animation_info.stage == 0 )
90         {
91             calculate_parent_animation_info(gs, rectsize,
92                     sort,
93                     animation_info.to,
94                     animation_info.to_calculated,
95                     gs.screen);
96         }
97     }
98 
99     if ( path in gs.animation_info && 
100             !gs.animation_info[path].to_calculated &&
101             (gs.animation_info[path].type == NameType.CreateDirectory ||
102              (gs.animation_info[path].type == NameType.Copy ||
103               gs.animation_info[path].type == NameType.Move) &&
104              gs.animation_info[path].stage == 1) )
105     {
106         calculate_directory_animation_info(gs, rectsize,
107                 sort,
108                 gs.animation_info[path].to, 
109                 gs.animation_info[path].to_calculated, 
110                 gs.screen);
111     }
112 
113     if ( path in gs.animation_info && 
114             !gs.animation_info[path].from_calculated &&
115             (gs.animation_info[path].type == NameType.Copy ||
116              gs.animation_info[path].type == NameType.Move) &&
117             gs.animation_info[path].stage == 0 )
118     {
119         calculate_directory_animation_info(gs, rectsize,
120                 sort,
121                 gs.animation_info[path].from, 
122                 gs.animation_info[path].from_calculated, 
123                 gs.screen);
124     }
125 }
126 
127 private string
128 size_to_string(ulong size)
129 {
130     string prefix;
131     double s;
132     if (size <= 1024UL)
133     {
134         prefix = "";
135         s = cast(double)size;
136     }
137     else if (size <= 1024UL*1024)
138     {
139         prefix = "Ki";
140         s = cast(double)size/1024.0;
141     }
142     else if (size <= 1024UL*1024*1024)
143     {
144         prefix = "Mi";
145         s = cast(double)size/(1024.0*1024.0);
146     }
147     else if (size <= 1024UL*1024*1024*1024)
148     {
149         prefix = "Gi";
150         s = cast(double)size/(1024.0*1024.0*1024.0);
151     }
152     else //if (size <= 1024UL*1024*1024*1024*1024)
153     {
154         prefix = "Ti";
155         s = cast(double)size/(1024.0*1024.0*1024.0*1024.0);
156     }
157 
158     return format("%.3f %sb", s, prefix);
159 }
160 
161 private int
162 calc_selected_and_draw_blue_rect(GlobalState gs, 
163         in string path, in ref SDL_Rect rect)
164 {
165     int selected = 0;
166     foreach(sel_path; gs.selection_hash.byKey())
167     {
168         if (sel_path > path && sel_path.startsWith(path))
169             selected++;
170     }
171 
172     if (selected > 0)
173     {
174         SDL_Rect dup_rect = rect;
175         unDE_RenderFillRect(gs.renderer, &dup_rect, 0x808080FF);
176     }
177 
178     return selected;
179 }
180 
181 private int
182 draw_fs_info(GlobalState gs,
183         in ref SDL_Rect_With_Not_Visible_And_No_Draw rnvnd,
184         in PathMnt path,
185         in ref LsblkInfo info,
186         bool not_scanned,
187         SortType sort,
188         int line = 0)
189 {
190     SDL_Rect rect = rnvnd.sdl_rect;
191 
192     rect.x = rect.x+rect.w/3;
193     rect.y = rect.y+rect.h/3;
194     rect.w /= 3;
195     rect.h /= 3;
196 
197     draw_line(gs, format("Name: %s", info.name), 
198                 0, (line++)*36, 12, rect);
199     draw_line(gs, format("Mountpoint: %s", info.mountpoint), 
200                 0, (line++)*36, 12, rect);
201     draw_line(gs, format("Filesystem: %s", info.fstype), 
202                 0, (line++)*36, 12, rect);
203     draw_line(gs, format("Label: %s", info.label), 
204                 0, (line++)*36, 12, rect);
205     draw_line(gs, format("UUID: %s", info.uuid), 
206                 0, (line++)*36, 12, rect);
207     line++;
208     draw_line(gs, format("Size: %s", size_to_string(info.size)), 
209                 0, (line++)*36, 12, rect);
210     draw_line(gs, format("Used: %s", size_to_string(info.used)), 
211                 0, (line++)*36, 12, rect);
212     draw_line(gs, format("Available: %s", size_to_string(info.avail)), 
213                 0, (line++)*36, 12, rect);
214     line++;
215 
216     if (not_scanned)
217     {
218         if (path !in gs.interface_flags)
219             gs.interface_flags[path] = false;
220 
221         draw_line(gs, (gs.interface_flags[path]?"☑":"☐") ~ " Scan all other file systems recursively", 
222                     0, (line)*36, 12, rect);
223 
224         auto tt = gs.text_viewer.font.get_char_from_cache(
225                 "☐", 12, SDL_Color(0xFF, 0xFF, 0xFF, 0xFF));
226 
227         SDL_Rect dst = SDL_Rect(rect.x, rect.y + (line*36) * rect.h/1024,
228                 cast(int)(tt.w*rect.w/1024.0/2), cast(int)(tt.h*rect.h/1024.0/2));
229 
230         void checkbox_clicked(GlobalState gs, int stage)
231         {
232             gs.interface_flags[path] = !gs.interface_flags[path];
233             gs.dirty = true;
234         }
235 
236         gs.new_clickable_list.insertFront(new Clickable(gs, dst, &checkbox_clicked));
237 
238         line++;
239         dst = draw_button(gs, 0, ((line)*36-4), 5*32, 42, rect);
240 
241         void button_clicked(GlobalState gs, int stage)
242         {
243             start_scan(gs, path);
244         }
245 
246         gs.new_clickable_list.insertFront(new Clickable(gs, dst, &button_clicked));
247         draw_line(gs, "  Scan", 0, (line++)*36, 12, rect);
248     }
249 
250     return line;
251 }
252 
253 private void
254 draw_de_info(GlobalState gs,
255         in ref SDL_Rect_With_Not_Visible_And_No_Draw rnvnd,
256         in PathMnt path,
257         in RectSize rectsize,
258         DirEntry de)
259 {
260     SDL_Rect rect = rnvnd.sdl_rect;
261 
262     rect.x = rect.x+rect.w/3;
263     rect.y = rect.y+rect.h/3;
264     rect.w /= 3;
265     rect.h /= 3;
266 
267     if (rect.w < 256) return;
268 
269     int selected = calc_selected_and_draw_blue_rect(gs, path, rect);
270 
271     if (rectsize.show_info == InfoType.Progress)
272         unDE_RenderFillRect(gs.renderer, &rect, 0x8080FF80);
273     else if ( rectsize.msg[0] != char.init && (rectsize.msg_time == 0 || rectsize.msg_time > gs.msg_stamp) )
274         unDE_RenderFillRect(gs.renderer, &rect, rectsize.msg_color);
275 
276     int line;
277     if (path in gs.lsblk)
278     {
279         line = draw_fs_info(gs, rnvnd, path, gs.lsblk[path], false, rectsize.sort, line);
280     }
281 
282     if (!de.isSymlink && de.isDir)
283     {
284         string name = path[path.lastIndexOf(SL)+1..$];
285         if (path == SL)
286             name = SL;
287 
288         draw_line(gs, format("Name: %s", name), 
289                     0, (line++)*36, 12, rect);
290         draw_line(gs, format("Sorted %s", rectsize.sort),
291                     0, (line++)*36, 12, rect);
292         draw_line(gs, format("Files: %s", rectsize.files), 
293                     0, (line++)*36, 12, rect);
294         line++;
295     }
296     if (!de.isSymlink)
297     {
298         if ( rectsize.size < 0 )
299         {
300             draw_line(gs, format("Scanning..."),
301                     0, (line++)*36, 12, rect);
302         }
303         else
304         {
305             draw_line(gs, format("Size: %s", size_to_string(rectsize.size)), 
306                         0, (line++)*36, 12, rect);
307             draw_line(gs, format("Disk Usage: %s", size_to_string(rectsize.disk_usage)), 
308                         0, (line++)*36, 12, rect);
309             version(DigitalMars)
310             {
311                 auto time = SysTime.fromUnixTime(rectsize.mtime);
312                 draw_line(gs, format("Mod. time: %s", time.toISOExtString()), 
313                             0, (line++)*36, 12, rect);
314             }
315             line++;
316 	    version (Posix)
317 	    {
318             string access = mode_to_string(de.statBuf.st_mode);
319 	    }
320 	    else version (Windows)
321 	    {
322             string access = "N/A";
323 	    }
324             auto access_rect = draw_line(gs, format("Access: "),
325                         0, (line)*36, 12, rect);
326 
327             foreach (int i, a; access)
328             {
329                 auto tt = gs.text_viewer.font.get_char_from_cache(
330                         ""~a, 12, SDL_Color(0xFF, 0xFF, 0xFF, 0xFF));
331 
332                 SDL_Rect dst = SDL_Rect(access_rect.x + access_rect.w, 
333                                         rect.y + (line*36) * rect.h/1024,
334                                         cast(int)(tt.w*rect.w/1024.0/2), 
335                                         cast(int)(tt.h*rect.h/1024.0/2));
336                 access_rect.x += dst.w;
337 
338                 auto r = SDL_RenderCopy(gs.renderer, tt.texture, 
339                                             null, &dst);
340                 if (r < 0)
341                 {
342                     writefln( "Error while render copy (access symbol): %s",
343                             SDL_GetError().to!string() );
344                 }
345 
346                 auto get_access_clicked(int i)
347                 {
348                     void access_clicked(GlobalState gs, int stage)
349                     {
350 			version(Posix)
351 			{
352                         mode_t new_mode = de.statBuf.st_mode ^ (1 << (11-i));
353 
354                         DirOrFile dof = DirOrFile.All;
355                         if (i == 5 || i == 8 || i == 11)
356                         {
357                             if (!de.isSymlink && de.isDir)
358                                 dof = DirOrFile.Dir;
359                             else
360                                 dof = DirOrFile.File;
361                         }
362 
363                         bool set = cast(bool)(new_mode & (1 << (11-i)));
364 
365                         string[] selection = gs.selection_hash.keys;
366                         int res = change_rights(gs, selection.idup(), set, i, dof, gs.interface_flags[path]);
367 
368                         if (res == 0)
369                         {
370                             res = chmod(path.toStringz, new_mode);
371                             if (res < 0)
372                             {
373                                 string msg = format("Chmod error: %s", fromStringz(strerror(errno)).idup());
374                                 gs.messages ~= ConsoleMessage(
375                                         SDL_Color(0xFF, 0x00, 0x00, 0xFF),
376                                         msg,
377                                         SDL_GetTicks()
378                                         );
379                                 writeln(msg);
380                             }
381                         }
382                         gs.dirty = true;
383 			}
384                     }
385                     return &access_clicked;
386                 }
387 
388                 gs.new_clickable_list.insertFront(new Clickable(gs, dst, get_access_clicked(i)));
389             }
390             line++;
391 
392 	    version(Posix)
393 	    {
394             draw_line(gs, format("User: %s", uid_to_name(de.statBuf.st_uid)),
395                         0, (line++)*36, 12, rect);
396             draw_line(gs, format("Group: %s", gid_to_name(de.statBuf.st_gid)),
397                         0, (line++)*36, 12, rect);
398             }
399 
400             if (path !in gs.interface_flags)
401                 gs.interface_flags[path] = false;
402 
403             auto tt = gs.text_viewer.font.get_char_from_cache(
404                     "☐", 12, SDL_Color(0xFF, 0xFF, 0xFF, 0xFF));
405 
406             SDL_Rect dst = SDL_Rect(rect.x, rect.y + (line*36) * rect.h/1024,
407                     cast(int)(tt.w*rect.w/1024.0/2), cast(int)(tt.h*rect.h/1024.0/2));
408 
409             draw_line(gs, format((gs.interface_flags[path]?"☑":"☐")~" Recursively change rights"),
410                         0, (line++)*36, 12, rect);
411 
412             void checkbox_clicked(GlobalState gs, int stage)
413             {
414                 gs.interface_flags[path] = !gs.interface_flags[path];
415                 gs.dirty = true;
416             }
417 
418             gs.new_clickable_list.insertFront(new Clickable(gs, dst, &checkbox_clicked));
419         }
420     }
421     line++;
422     if (selected > 0)
423         draw_line(gs, format("Selected %d items", selected), 
424                     0, (line++)*36, 12, rect);
425         line++;
426 
427     if (rectsize.show_info == InfoType.Progress)
428     {
429         draw_line(gs, from_char_array(rectsize.path), 
430                     0, (line++)*36, 12, rect);
431 
432         draw_line(gs, format("Progress: %.2f %%", cast(double)rectsize.progress/100),
433                     0, (line++)*36, 12, rect);
434 
435         long estimate_time = rectsize.estimate_end - Clock.currTime().toUnixTime();
436         long secs = estimate_time%60;
437         long mins = estimate_time/60%60;
438         long hours = estimate_time/3600;
439 
440         draw_line(gs, format("Estimate Time: %d:%02d:%02d", hours, mins, secs),
441                     0, (line++)*36, 12, rect);
442     }
443     else if (rectsize.msg[0] != char.init && (rectsize.msg_time == 0 || rectsize.msg_time > gs.msg_stamp) )
444     {
445         draw_line(gs, from_char_array(rectsize.msg), 
446                     0, (line++)*36, 12, rect);
447     }
448 }
449 
450 private void
451 draw_enter_name(GlobalState gs,
452         in ref SDL_Rect_With_Not_Visible_And_No_Draw rnvnd,
453         in string path0,
454         in RectSize rectsize)
455 {
456     SDL_Rect rect = rnvnd.sdl_rect;
457 
458     rect.x = rect.x+rect.w/3;
459     rect.y = rect.y+rect.h/3;
460     rect.w /= 3;
461     rect.h /= 3;
462 
463     if (rect.w < 256) return;
464 
465     unDE_RenderFillRect(gs.renderer, &rect, 0x80FFFFFF);
466 
467     if (gs.current_path !in gs.enter_names)
468     {
469         switch (rectsize.show_info)
470         {
471             case InfoType.CreateDirectory:
472                 gs.enter_names[gs.current_path] = EnterName(NameType.CreateDirectory, "", 0);
473                 break;
474             case InfoType.Copy:
475                 gs.enter_names[gs.current_path] = EnterName(NameType.Copy, "", 0);
476                 break;
477             case InfoType.Move:
478                 gs.enter_names[gs.current_path] = EnterName(NameType.Move, "", 0);
479                 break;
480             default:
481                 assert(false);
482         }
483         writefln("Not in enter_names %s", gs.current_path);
484     }
485 
486     with(gs.enter_names[gs.current_path])
487     {
488         draw_direntry_name(gs, name[0..pos]~"|"~name[pos..$], rnvnd, true);
489     }
490 }
491 
492 private void
493 draw_link_info(GlobalState gs,
494         in ref SDL_Rect_With_Not_Visible_And_No_Draw rnvnd,
495         in string path)
496 {
497     version(Posix)
498     {
499     SDL_Rect rect = rnvnd.sdl_rect;
500 
501     /*rect.x = rect.x+rect.w/3;
502     rect.y = rect.y+rect.h/3;
503     rect.w /= 3;
504     rect.h /= 3;*/
505 
506     if (rect.w < 256) return;
507 
508     string name = path[path.lastIndexOf(SL)+1..$];
509     if (path == SL)
510         name = SL;
511 
512     int line;
513     draw_line(gs, format("Name: %s", name), 
514                 0, (line++)*36, 12, rect);
515     draw_line(gs, format("Link to: %s", readLink(path)), 
516             0, (line++)*36, 12, rect);
517     }
518 }
519 
520 private bool
521 isSelected(GlobalState gs, in ref RectSize rectsize,
522         in string path, SortType sort)
523 {
524 
525     bool selected = false;
526     bool full_selected = false;
527     
528     foreach(i, sel; gs.selection_list)
529     {
530         string parent = getParent(path);
531         string par_from = getParent(sel.from);
532         string par_to = getParent(sel.to);
533 
534         if (parent == par_from && parent == par_to)
535         {
536             if (sel.sort == SortType.BySize)
537             {
538                 if (rectsize.disk_usage <= sel.size_from &&
539                         rectsize.disk_usage >= sel.size_to &&
540                         (rectsize.disk_usage == sel.size_from ?
541                          path >= sel.from : 1) &&
542                         (rectsize.disk_usage == sel.size_to ?
543                          path <= sel.to : 1))
544                 {
545                     if (i < gs.selection_lsof)
546                         selected = !selected;
547                     full_selected = !full_selected;
548                 }
549             }
550             else if (sel.sort == SortType.ByTime)
551             {
552                 if (rectsize.mtime <= sel.mtime_from &&
553                         rectsize.mtime >= sel.mtime_to &&
554                         (rectsize.mtime == sel.mtime_from ?
555                          path >= sel.from : 1) &&
556                         (rectsize.mtime == sel.mtime_to ?
557                          path <= sel.to : 1))
558                 {
559                     if (i < gs.selection_lsof)
560                         selected = !selected;
561                     full_selected = !full_selected;
562                 }
563             }
564             else
565             {
566                 if (path >= sel.from && path <= sel.to )
567                 {
568                     if (i < gs.selection_lsof)
569                         selected = !selected;
570                     full_selected = !full_selected;
571                 }
572             }
573         }
574     }
575 
576     if (gs.selection_finish == 2 && selected)
577     {
578         if (path in gs.selection_hash)
579             gs.selection_hash.remove(path);
580         else
581             /* EN: FIXME: THe position of selected item can be changed
582                by changing sort ype
583                RU: ИСПРАВЬ_МЕНЯ: позиция выбранного элемента может
584                изменится при изменении порядка сортировки */
585             gs.selection_hash[path] = rectsize.rect(sort);
586 
587         full_selected = !full_selected;
588         gs.dirty = true;
589     }
590 
591 
592     return full_selected;
593 }
594 
595 version(Windows)
596 {
597 int
598 draw_my_computer(GlobalState gs, DbTxn txn, 
599         PathMnt path,
600         ref CoordinatesPlusScale surf,
601         DRect apply_rect,
602         SortType sort = SortType.ByName,
603         bool current = false,
604         bool fast = false, int level = 0)
605 {
606 	RectSize rectsize = RectSize(apply_rect, apply_rect, apply_rect);
607 	DRect full_rect = DRect(0, 0, 1024*1024, 1024*1024);
608 
609 	int ret = 0;
610 
611 	string[] paths;
612 
613 	foreach(char c; 'A'..'Z'+1)
614 	{
615 		string disk = c ~ ":";
616 		if ( disk in gs.lsblk )
617 		{
618 			paths ~= disk;
619 		}
620 	}
621 
622 	int levels = 0;
623 	long l = paths.length;
624 	/*if (l > 0)
625 	  {
626 	  l--;
627 	  }*/
628 	for (long i=12; i < l; i=i*2+12)
629 	{
630 		l -= i;
631 		levels++;
632 	}
633 	if (l > 0) levels++;
634 
635 	immutable long entries_on_last_level = l;
636 
637 	//RectSize[string] rect_sizes;
638 
639 	//writefln("levels = %s", levels);
640 	long[] coords = new long[levels+1];
641 	RectSize full_size = RectSize(drect_zero, drect_zero, drect_zero, 
642 			0, 0,
643 			0, 0, 0);
644 	long i = 0;
645 	size_t lev = 1;
646 	bool first = true;
647 	foreach (string name; paths)
648 	{
649 		DRect rect;
650 		calculate_rect(full_rect, rect, coords, lev);
651 
652 		auto res = draw_path(gs, txn, path.next(name),
653 				surf, rect,
654 				rectsize.sort,
655 				false,
656 				ret < 0, level+1);
657 		if (res == -1)
658 		{
659 			fast = true;
660 			ret = -1;
661 		}
662 
663 		//full_size.size += rect_size.size;
664 		//full_size.disk_usage += rect_size.disk_usage;
665 		//full_size.files += rect_size.files;
666 
667 		//rect_sizes[name] = rect_size;
668 
669 		calculate_coords(coords, lev, i, levels, entries_on_last_level);
670 		first = false;
671 	}
672 
673 	bool selected = isSelected(gs, rectsize, path, sort);
674 	auto rnvnd = draw_rect_with_color_by_size(
675 			gs, rectsize, sort, surf, 
676 			(cast(bool)(path in gs.selection_hash)) ^ selected,
677 			path);
678 
679 	draw_fs_info(gs, rnvnd, path, gs.lsblk[path], false, SortType.ByName);
680 
681         draw_direntry_name(gs, gs.lsblk[path].name, rnvnd);
682 
683 	return ret;
684 }
685 
686 }
687 
688 int
689 draw_path(GlobalState gs, DbTxn txn, 
690         PathMnt path,
691         ref CoordinatesPlusScale surf,
692         DRect apply_rect,
693         SortType sort = SortType.ByName,
694         bool current = false,
695         bool fast = false, int level = 0)
696 {
697     Fiber.yield();
698 
699     int ret = 0;
700     if (level == 0)
701     {
702         clear_image_cache(gs);
703     }
704 
705     DirEntry de;
706     bool no_de = false;
707     try
708     {
709         de = DirEntry(path);
710     }
711     catch (FileException e)
712     {
713 	no_de = true;
714     }
715 
716     bool pmnt;
717     Dbt key, data;
718     if (path in gs.lsblk)
719     {
720         check_if_fully_scanned(gs, path);
721 
722         string path0 = path.get_key(gs.lsblk);
723         key = path0;
724         auto res = gs.db_map.get(txn, &key, &data);
725         if (res == 0)
726         {
727             RectSize rectsize;
728             rectsize = data.to!(RectSize);
729             apply_rect = rectsize.rect(sort).apply(apply_rect);
730         }
731         else
732         {
733             // TODO: Created mount point, rescan up path
734         }
735 
736         pmnt = true;
737     }
738 
739     try
740     {
741         path.update(gs.lsblk);
742     }
743     catch (Exception e)
744     {
745         return 0;
746     }
747 
748     version(Windows)
749     {
750 	    if (path._next == SL)
751 	    {
752 		    return draw_my_computer(gs, txn, 
753 				    path, surf, apply_rect, sort, current, fast, level);
754 	    }
755     }
756 
757     string path0 = path.get_key(gs.lsblk);
758     key = path0;
759     //writefln("GET %s", path0.replace("\0", SL));
760     auto res = gs.db_map.get(txn, &key, &data);
761 
762     if (res == 0)
763     {
764         RectSize rectsize;
765         RectSize origrectsize;
766         origrectsize = rectsize = data.to!(RectSize);
767         rectsize.rect(sort) = rectsize.rect(sort).apply(apply_rect);
768         //writefln("path=%s, rect=%s", path, rectsize.rect(sort));
769         //writefln("rectsize=%s", rectsize);
770 
771         bool selected = isSelected(gs, rectsize, path, sort);
772         auto rnvnd = draw_rect_with_color_by_size(
773                 gs, rectsize, sort, surf, 
774                 (cast(bool)(path in gs.selection_hash)) ^ selected,
775                 path);
776 
777         if (path in gs.selection_sub)
778         {
779             draw_center_rect_with_color(
780                     gs, rnvnd, 0x8080FFFF);
781         }
782 
783         try{
784             if (no_de)
785             {
786                 draw_center_rect_with_color(
787                         gs, rnvnd, 0x808080FF);
788             }
789             else if (!de.isSymlink && de.isDir && rectsize.show_info == InfoType.Progress)
790             {
791                 draw_center_rect_with_color(
792                         gs, rnvnd, 0x8080FF80);
793             }
794             else if (!de.isSymlink && de.isDir && rectsize.newest_msg_time > gs.msg_stamp ||
795                     rectsize.msg[0] != char.init && rectsize.msg_time > gs.msg_stamp)
796             {
797                 draw_center_rect_with_color(
798                         gs, rnvnd, 0x80FF8080);
799             }
800         }
801         catch (Exception e)
802         {
803             return ret;
804         }
805 
806         calculate_animation_info(gs, rectsize, path, sort);
807 
808         void direntry_selected(GlobalState gs, int stage)
809         {
810             if (stage == 0)
811             {
812                 Selection sel;
813                 sel.from = path;
814                 sel.to = path;
815                 sel.sort = sort;
816                 if (sort == SortType.BySize)
817                 {
818                     sel.size_from = rectsize.disk_usage;
819                     sel.size_to = rectsize.disk_usage;
820                 }
821                 else if (sort == SortType.ByTime)
822                 {
823                     sel.mtime_from = rectsize.mtime;
824                     sel.mtime_to = rectsize.mtime;
825                 }
826                 gs.selection_list ~= sel;
827                 //writefln("%s Selection %s", stage, sel);
828             }
829             else if ( (stage == 1 || stage == 2) &&
830                     gs.selection_list.length > 0)
831             {
832                 Selection* sel = &gs.selection_list[$-1];
833 
834                 sel.to = path;
835                 if (sel.sort == SortType.BySize)
836                     sel.size_to = rectsize.disk_usage;
837                 else if (sel.sort == SortType.ByTime)
838                     sel.mtime_to = rectsize.mtime;
839 
840                 gs.redraw_fast = true;
841                 //writefln("%s Selection %s", stage, *sel);
842             }
843 
844             if ( stage == 2)
845             {
846                 if (gs.selection_finish == 0)
847                     gs.selection_finish++;
848                 gs.redraw_fast = false;
849             }
850 
851             gs.selection_stage = stage;
852             gs.dirty = true;
853         }
854 
855         void run_viewer(GlobalState gs, int stage)
856         {
857             openFile(gs, path);
858         }
859 
860         void switch_info(GlobalState gs, int stage)
861         {
862             if (origrectsize.show_info == InfoType.None)
863                 origrectsize.show_info = InfoType.FileInfo;
864             else if (origrectsize.show_info == InfoType.FileInfo)
865                 origrectsize.show_info = InfoType.None;
866 
867             key = path0;
868             data = origrectsize;
869             auto res = gs.db_map.put(txn, &key, &data);
870             if (res != 0)
871                 throw new Exception("Path info to map-db not written");
872         }
873 
874         void run_mime(GlobalState gs, int stage)
875         {
876             openFileByMime(gs, path);
877         }
878 
879         if (rnvnd.current_path)
880         {
881             //writefln("current_path = %s", path0.replace("\0",SL));
882             gs.current_path = path0;
883             gs.full_current_path = path;
884             current = false;
885         }
886 
887         if (current || rnvnd.current_path && (!no_de && !de.isSymlink && !de.isDir))
888         {
889             gs.new_double_clickable_list.insertFront(new Clickable(gs, rnvnd.sdl_rect, &run_viewer));
890             gs.new_right_clickable_list.insertFront(new Clickable(gs, rnvnd.sdl_rect, &direntry_selected));
891         }
892 
893         if ((current || rnvnd.current_path) && (!no_de && !de.isDir))
894         {
895             gs.new_double_right_clickable_list.insertFront(new Clickable(gs, rnvnd.sdl_rect, &switch_info));
896         }
897 
898         if ((current || rnvnd.current_path) && (!no_de && !de.isSymlink && !de.isDir))
899         {
900             gs.new_middle_clickable_list.insertFront(new Clickable(gs, rnvnd.sdl_rect, &run_mime));
901         }
902 
903         if (rnvnd.no_draw)
904         {
905             if (rectsize.show_info == InfoType.CreateDirectory ||
906                     rectsize.show_info == InfoType.Copy ||
907                     rectsize.show_info == InfoType.Move)
908             {
909                 draw_enter_name(gs, rnvnd, path0, rectsize);
910             }
911             else if (!no_de && !de.isSymlink && de.isDir)
912             {
913                 draw_de_info(gs, rnvnd, path, rectsize, de);
914             }
915         }
916 
917         /* EN: Only if it is not symbolic link and not too small rectangle
918            draw directory recursively
919            RU: Если только это не символическая ссылка или не слишком мелкий
920            прямоугольник, рисовать директорию рекурсивно */
921         if (!no_de && de.isSymlink)
922         {
923         version(Posix)
924         {
925             draw_link_info(gs, rnvnd, path);
926 
927             string link_to = readLink(path);
928             try
929             {
930                 if (link_to > "" && (gs.redraw_fast ? level < 2 : level < 3))
931                 {
932                     if (link_to[0] != SL[0]) link_to = getParent(path)~SL~link_to;
933                     if (link_to[$-1] == SL[0]) link_to = link_to[0..$-1];
934 
935                     link_to = replaceAll(link_to, regex(`/\./`), SL);
936                     link_to = replaceAll(link_to, regex(`/\.$`), "");
937                     auto re = regex(`/[^/]*/\.\.`);
938                     string old_link_to;
939                     do
940                     {
941                         old_link_to = link_to;
942                         link_to = replaceAll(link_to, re, "");
943                     }
944                     while (old_link_to !is link_to);
945                     DRect linkrect = rectsize.rect(sort);
946                     if (link_to == "") link_to = SL;
947 
948                     res = draw_path(gs, txn, PathMnt(gs.lsblk, link_to),
949                             surf, linkrect,
950                             rectsize.sort,
951                             rnvnd.current_path,
952                             ret < 0, level+1);
953                 }
954             } catch (UTFException e)
955             {
956                 writefln("UTFException: %s", link_to);
957             }
958 	    }
959         }
960         else if (!no_de && de.isDir &&
961                 (rnvnd.sdl_rect.w >= 4 || rnvnd.sdl_rect.h >= 4) &&
962                 !rnvnd.not_visible)
963         {
964             string[] paths;
965 
966             if (gs.redraw_fast ? level < 2 : level < 3)
967             {
968                 try{
969                     foreach (string name; dirEntries(path, SpanMode.shallow))
970                     {
971                         res = draw_path(gs, txn, path.next(name),
972                                 surf, rectsize.rect(sort),
973                                 rectsize.sort,
974                                 rnvnd.current_path,
975                                 ret < 0, level+1);
976                         if (res == -1)
977                         {
978                             fast = true;
979                             ret = -1;
980                         }
981                         else if (res == -2 && rectsize.size >= 0)
982                         {
983                             rescan_path(gs, path);
984                         }
985                     }
986                 } catch (FileException e)
987                 {
988                 }
989             }
990         }
991 
992         if (!no_de && !de.isSymlink && de.isFile &&
993                 rnvnd.sdl_rect.w > 256 && !rnvnd.not_visible )
994         {
995             if (rectsize.show_info == InfoType.FileInfo ||
996                     rectsize.show_info == InfoType.Progress)
997             {
998                 draw_de_info(gs, rnvnd, path, rectsize, de);
999             }
1000             else
1001             {
1002                 bool is_picture = draw_picture(gs, rnvnd, path, fast, ret);
1003                 if (!is_picture)
1004                     draw_text_file(gs, rnvnd, path, ret);
1005             }
1006         }
1007 
1008         string name = path[path.lastIndexOf(SL)+1..$];
1009         if (path == SL)
1010             name = SL;
1011         draw_direntry_name(gs, name, rnvnd);
1012     }
1013     else if (pmnt)
1014     {
1015         RectSize rectsize;
1016         rectsize.rect(sort) = apply_rect;
1017         rectsize.size = -1;
1018         bool selected = isSelected(gs, rectsize, path, sort);
1019         auto rnvnd = draw_rect_with_color_by_size(
1020                 gs, rectsize, sort, surf, 
1021                 (cast(bool)(path in gs.selection_hash)) ^ selected,
1022                 path);
1023 
1024         draw_fs_info(gs, rnvnd, path, gs.lsblk[path], true, SortType.ByName);
1025 
1026         string name = path[path.lastIndexOf(SL)+1..$];
1027         if (path == SL)
1028             name = SL;
1029         draw_direntry_name(gs, name, rnvnd);
1030     }
1031     else
1032     {
1033         if (false/*gs.copiers.length > 0 || gs.movers.length > 0*/)
1034         {
1035             // Nothing to do
1036         }
1037         else
1038         {
1039             //writefln("Can't find %s", path0.replace("\0", SL));
1040             ret = -2;
1041         }
1042     }
1043 
1044     return ret;
1045 }