1 // File management class with progress dialog
2 
3 #include "config.h"
4 #include "i18n.h"
5 
6 #include <fcntl.h>
7 #include <utime.h>
8 #if defined(linux)
9 #include <sys/statfs.h>
10 #endif
11 
12 // For Sun compatibility
13 #ifdef __sun
14 #include <alloca.h>
15 #endif
16 
17 
18 #include <fx.h>
19 #include <fxkeys.h>
20 #include <FXPNGIcon.h>
21 #include <FXUTF8Codec.h>
22 
23 #include "xfedefs.h"
24 #include "icons.h"
25 #include "xfeutils.h"
26 #include "OverwriteBox.h"
27 #include "MessageBox.h"
28 #include "CommandWindow.h"
29 #include "File.h"
30 
31 
32 // Delay before the progress bar should be shown (ms)
33 #define SHOW_PROGRESSBAR_DELAY    1000
34 
35 // Progress dialog width
36 #define PROGRESSDIALOG_WIDTH      200
37 
38 
39 
40 // Message Map
41 FXDEFMAP(File) FileMap[] =
42 {
43     FXMAPFUNC(SEL_COMMAND, File::ID_CANCEL_BUTTON, File::onCmdCancel),
44     FXMAPFUNC(SEL_TIMEOUT, File::ID_TIMEOUT, File::onTimeout),
45 };
46 
47 // Object implementation
FXIMPLEMENT(File,DialogBox,FileMap,ARRAYNUMBER (FileMap))48 FXIMPLEMENT(File, DialogBox, FileMap, ARRAYNUMBER(FileMap))
49 
50 // Construct object
51 File::File(FXWindow* owner, FXString title, const FXuint operation, const FXuint num) : DialogBox(owner, title, DECOR_TITLE|DECOR_BORDER|DECOR_STRETCHABLE)
52 {
53     // Progress window
54     FXPacker* buttons = new FXPacker(this, LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X, 0, 0, 10, 10, PROGRESSDIALOG_WIDTH, PROGRESSDIALOG_WIDTH, 5, 5);
55     FXVerticalFrame* contents = new FXVerticalFrame(this, LAYOUT_SIDE_TOP|FRAME_NONE|LAYOUT_FILL_X|LAYOUT_FILL_Y);
56 
57     // Cancel Button
58     cancelButton = new FXButton(buttons, _("&Cancel"), NULL, this, File::ID_CANCEL_BUTTON, FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X, 0, 0, 0, 0, 20, 20);
59     cancelButton->setFocus();
60     cancelButton->addHotKey(KEY_Escape);
61     cancelled = false;
62 
63     // Progress bar
64     progressbar = NULL;
65 
66     // Progress bar colors (foreground and background)
67     FXuint  r, g, b, l;
68     FXColor textcolor, textaltcolor;
69     FXColor fgcolor = getApp()->reg().readColorEntry("SETTINGS", "pbarcolor", FXRGB(0, 0, 255));
70     FXColor bgcolor = getApp()->reg().readColorEntry("SETTINGS", "backcolor", FXRGB(255, 255, 255));
71 
72     // Text color is white or black depending on the background luminance
73     r = FXREDVAL(bgcolor);
74     g = FXGREENVAL(bgcolor);
75     b = FXBLUEVAL(bgcolor);
76     l = (FXuint)(0.3*r+0.59*g+0.11*b);
77     if (l < 150)
78     {
79         textcolor = FXRGB(255, 255, 255);
80     }
81     else
82     {
83         textcolor = FXRGB(0, 0, 0);
84     }
85 
86     // Alternate text color is white or black depending on the foreground luminance
87     r = FXREDVAL(fgcolor);
88     g = FXGREENVAL(fgcolor);
89     b = FXBLUEVAL(fgcolor);
90     l = (FXuint)(0.3*r+0.59*g+0.11*b);
91     if (l < 150)
92     {
93         textaltcolor = FXRGB(255, 255, 255);
94     }
95     else
96     {
97         textaltcolor = FXRGB(0, 0, 0);
98     }
99 
100     // Progress dialog depends on the file operation
101     switch (operation)
102     {
103     case COPY:
104         // Labels and progress bar
105         uplabel = new FXLabel(contents, _("Source:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
106         downlabel = new FXLabel(contents, _("Target:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
107         progressbar = new FXProgressBar(contents, NULL, 0, LAYOUT_FILL_X|FRAME_SUNKEN|FRAME_THICK|PROGRESSBAR_PERCENTAGE, 0, 0, 0, 0, PROGRESSDIALOG_WIDTH);
108         progressbar->setBarColor(fgcolor);
109         progressbar->setTextColor(textcolor);
110         progressbar->setTextAltColor(textaltcolor);
111         datatext = _("Copied data:");
112         datalabel = new FXLabel(contents, datatext, NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
113 
114         // Timer on
115         getApp()->addTimeout(this, File::ID_TIMEOUT, SHOW_PROGRESSBAR_DELAY);
116         break;
117 
118     case MOVE:
119         // Labels and progress bar
120         uplabel = new FXLabel(contents, _("Source:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
121         downlabel = new FXLabel(contents, _("Target:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
122         progressbar = new FXProgressBar(contents, NULL, 0, LAYOUT_FILL_X|FRAME_SUNKEN|FRAME_THICK|PROGRESSBAR_PERCENTAGE, 0, 0, 0, 0, PROGRESSDIALOG_WIDTH);
123         progressbar->setBarColor(fgcolor);
124         progressbar->setTextColor(textcolor);
125         progressbar->setTextAltColor(textaltcolor);
126         datatext = _("Moved data:");
127         datalabel = new FXLabel(contents, datatext, NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
128 
129         // Timer on
130         getApp()->addTimeout(this, File::ID_TIMEOUT, SHOW_PROGRESSBAR_DELAY);
131         break;
132 
133     case DELETE:
134         // Labels
135         uplabel = new FXLabel(contents, _("Delete:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
136         downlabel = new FXLabel(contents, _("From:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
137         datalabel = NULL;
138 
139         // Timer on
140         getApp()->addTimeout(this, File::ID_TIMEOUT, SHOW_PROGRESSBAR_DELAY);
141         break;
142 
143     case CHMOD:
144         // Labels
145         uplabel = new FXLabel(contents, _("Changing permissions..."), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
146         downlabel = new FXLabel(contents, _("File:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
147         datalabel = NULL;
148 
149         // Timer on
150         getApp()->addTimeout(this, File::ID_TIMEOUT, SHOW_PROGRESSBAR_DELAY);
151         break;
152 
153     case CHOWN:
154         // Labels
155         uplabel = new FXLabel(contents, _("Changing owner..."), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
156         downlabel = new FXLabel(contents, _("File:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
157         datalabel = NULL;
158 
159         // Timer on
160         getApp()->addTimeout(this, File::ID_TIMEOUT, SHOW_PROGRESSBAR_DELAY);
161         break;
162 
163 #if defined(linux)
164     case MOUNT:
165         // Labels
166         uplabel = new FXLabel(contents, _("Mount file system..."), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
167         downlabel = new FXLabel(contents, _("Mount the folder:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
168         datalabel = NULL;
169         break;
170 
171     case UNMOUNT:
172         // Labels
173         uplabel = new FXLabel(contents, _("Unmount file system..."), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
174         downlabel = new FXLabel(contents, _("Unmount the folder:"), NULL, JUSTIFY_LEFT|LAYOUT_FILL_X);
175         datalabel = NULL;
176         break;
177 #endif
178 
179     default: // Other : RENAME, SYMLINK, ARCHIVE, EXTRACT, PKG_INSTALL, PKG_UNINSTALL
180         // Progress dialog not used
181         uplabel = NULL;
182         downlabel = NULL;
183         datalabel = NULL;
184     }
185 
186     FXbool confirm_overwrite = getApp()->reg().readUnsignedEntry("OPTIONS", "confirm_overwrite", true);
187 
188     // Initialize the overwrite flags
189     if (confirm_overwrite)
190     {
191         overwrite = false;
192         overwrite_all = false;
193         skip_all = false;
194     }
195     else
196     {
197         overwrite = true;
198         overwrite_all = true;
199         skip_all = false;
200     }
201 
202     // Total data read
203     totaldata = 0;
204 
205     // Owner window
206     ownerwin = owner;
207 
208 	// Number of selected items
209 	numsel = num;
210 
211     // Error message box
212     mbox = new MessageBox(ownerwin, _("Error"), "", errorbigicon, BOX_OK_CANCEL|DECOR_TITLE|DECOR_BORDER);
213 }
214 
215 
216 // Destructor
~File()217 File::~File()
218 {
219     getApp()->removeTimeout(this, File::ID_TIMEOUT);
220     delete progressbar;
221     delete mbox;
222 }
223 
224 
225 // Create and initialize
create()226 void File::create()
227 {
228     DialogBox::create();
229 }
230 
231 
232 // Force timeout for progress dialog (used before opening confirmation or error dialogs)
forceTimeout(void)233 void File::forceTimeout(void)
234 {
235     getApp()->removeTimeout(this, File::ID_TIMEOUT);
236     hide();
237     getApp()->forceRefresh();
238     getApp()->flush();
239 }
240 
241 
242 // Restart timeout for progress dialog  (used after closing confirmation or error dialogs)
restartTimeout(void)243 void File::restartTimeout(void)
244 {
245     getApp()->addTimeout(this, File::ID_TIMEOUT, SHOW_PROGRESSBAR_DELAY);
246 }
247 
248 
249 // Read bytes
fullread(int fd,FXuchar * ptr,FXlong len)250 FXlong File::fullread(int fd, FXuchar* ptr, FXlong len)
251 {
252     FXlong nread;
253 
254 #ifdef EINTR
255     do
256     {
257         nread = read(fd, ptr, len);
258     } while (nread < 0 && errno == EINTR);
259 #else
260     nread = read(fd, ptr, len);
261 #endif
262     return(nread);
263 }
264 
265 
266 // Write bytes
fullwrite(int fd,const FXuchar * ptr,FXlong len)267 FXlong File::fullwrite(int fd, const FXuchar* ptr, FXlong len)
268 {
269     FXlong nwritten, ntotalwritten = 0;
270 
271     while (len > 0)
272     {
273         nwritten = write(fd, ptr, len);
274         if (nwritten < 0)
275         {
276 #ifdef EINTR
277             if (errno == EINTR)
278             {
279                 continue;
280             }
281 #endif
282             return(-1);
283         }
284         ntotalwritten += nwritten;
285         ptr += nwritten;
286         len -= nwritten;
287     }
288     return(ntotalwritten);
289 }
290 
291 
292 // Construct overwrite dialog and get user answer
getOverwriteAnswer(FXString srcpath,FXString tgtpath)293 FXuint File::getOverwriteAnswer(FXString srcpath, FXString tgtpath)
294 {
295     // Message string
296     FXString msg;
297 
298     if (::isDirectory(tgtpath))
299     {
300         msg.format(_("Folder %s already exists.\nOverwrite?\n=> Caution, files within this folder could be overwritten!"), tgtpath.text());
301     }
302     else
303     {
304         msg.format(_("File %s already exists.\nOverwrite?"), tgtpath.text());
305     }
306 
307     // Read time format
308     FXString timeformat = getApp()->reg().readStringEntry("SETTINGS", "time_format", DEFAULT_TIME_FORMAT);
309 
310     // Get the size and mtime of the source and target
311     struct stat linfo;
312     FXString    srcsize, srcmtime, tgtsize, tgtmtime;
313     FXbool      statsrc = false, stattgt = false;
314     if (lstatrep(srcpath.text(), &linfo) == 0)
315     {
316         statsrc = true;
317         srcmtime = FXSystem::time(timeformat.text(), linfo.st_mtime);
318         char buf[MAXPATHLEN];
319         if (S_ISDIR(linfo.st_mode)) // Folder
320         {
321             FXulong dirsize = 0;
322             FXuint  nbfiles = 0, nbsubfolders = 0;
323             FXulong totalsize=0;
324             strlcpy(buf, srcpath.text(), srcpath.length()+1);
325             dirsize = pathsize(buf, &nbfiles, &nbsubfolders,&totalsize);
326 #if __WORDSIZE == 64
327             snprintf(buf, sizeof(buf), "%lu", dirsize);
328 #else
329             snprintf(buf, sizeof(buf), "%llu", dirsize);
330 #endif
331         }
332         else // File
333 #if __WORDSIZE == 64
334         {
335             snprintf(buf, sizeof(buf), "%lu", (FXulong)linfo.st_size);
336         }
337 #else
338         {
339             snprintf(buf, sizeof(buf), "%llu", (FXulong)linfo.st_size);
340         }
341 #endif
342         srcsize = ::hSize(buf);
343     }
344     if (lstatrep(tgtpath.text(), &linfo) == 0)
345     {
346         stattgt = true;
347         tgtmtime = FXSystem::time(timeformat.text(), linfo.st_mtime);
348         char buf[64];
349         if (S_ISDIR(linfo.st_mode)) // Folder
350         {
351             FXulong dirsize = 0;
352             FXuint  nbfiles = 0, nbsubfolders = 0;
353             FXulong totalsize=0;
354 
355             strlcpy(buf, tgtpath.text(), tgtpath.length()+1);
356             dirsize = pathsize(buf, &nbfiles, &nbsubfolders,&totalsize);
357 #if __WORDSIZE == 64
358             snprintf(buf, sizeof(buf), "%lu", dirsize);
359 #else
360             snprintf(buf, sizeof(buf), "%llu", dirsize);
361 #endif
362         }
363         else // File
364 #if __WORDSIZE == 64
365         {
366             snprintf(buf, sizeof(buf), "%lu", (FXulong)linfo.st_size);
367         }
368 #else
369         {
370             snprintf(buf, sizeof(buf), "%llu", (FXulong)linfo.st_size);
371         }
372 #endif
373         tgtsize = ::hSize(buf);
374     }
375 
376     // Overwrite dialog
377     OverwriteBox* dlg;
378     if (statsrc && stattgt)
379     {
380         if (numsel == 1)
381         {
382 	        dlg = new OverwriteBox(ownerwin, _("Confirm Overwrite"), msg, srcsize, srcmtime, tgtsize, tgtmtime, OVWBOX_SINGLE_FILE);
383 		}
384 		else
385 		{
386 	        dlg = new OverwriteBox(ownerwin, _("Confirm Overwrite"), msg, srcsize, srcmtime, tgtsize, tgtmtime);
387 		}
388     }
389     else
390     {
391         if (numsel == 1)
392         {
393 	        dlg = new OverwriteBox(ownerwin, _("Confirm Overwrite"), msg, OVWBOX_SINGLE_FILE);
394 		}
395 		else
396 		{
397 	        dlg = new OverwriteBox(ownerwin, _("Confirm Overwrite"), msg);
398 		}
399     }
400 
401     FXuint answer = dlg->execute(PLACEMENT_OWNER);
402     delete dlg;
403     restartTimeout();
404 
405     return(answer);
406 }
407 
408 
409 // Copy ordinary file
410 // Return  0 to allow displaying an error message in the calling function
411 // Return -1 to prevent displaying an error message in the calling function
412 // Return -2 when an error has occurred during the copy
copyfile(const FXString & source,const FXString & target,const FXbool preserve_date)413 int File::copyfile(const FXString& source, const FXString& target, const FXbool preserve_date)
414 {
415     FXString       destfile;
416     FXuchar        buffer[32768];
417     struct stat    info;
418     struct utimbuf timbuf;
419     FXlong         nread, nwritten;
420     FXlong         size, dataread = 0;
421     int            src, dst;
422     int            ok = false;
423 
424     FXbool warn = getApp()->reg().readUnsignedEntry("OPTIONS", "preserve_date_warn", true);
425 
426     if ((src = ::open(source.text(), O_RDONLY)) >= 0)
427     {
428         if (statrep(source.text(), &info) == 0)
429         {
430             // If destination is a directory
431             if (::isDirectory(target))
432             {
433                 destfile = target+PATHSEPSTRING+FXPath::name(source);
434             }
435             else
436             {
437                 destfile = target;
438             }
439 
440             // Copy file block by block
441             size = info.st_size;
442             if ((dst = ::open(destfile.text(), O_WRONLY|O_CREAT|O_TRUNC, info.st_mode)) >= 0)
443             {
444  			   	int error = false;
445 
446                 while (1)
447                 {
448                     errno = 0;
449                     nread = File::fullread(src, buffer, sizeof(buffer));
450                     int errcode = errno;
451                     if (nread < 0)
452                     {
453                         forceTimeout();
454 
455                         FXString str;
456                         if (errcode)
457                         {
458                             str.format(_("Can't copy file %s: %s"), target.text(), strerror(errcode));
459                         }
460                         else
461                         {
462                             str.format(_("Can't copy file %s"), target.text());
463                         }
464                         mbox->setText(str);
465                         FXuint answer = mbox->execute(PLACEMENT_OWNER);
466 
467                         restartTimeout();
468                         if (answer == BOX_CLICKED_CANCEL)
469                         {
470                             ::close(dst);
471                             ::close(src);
472                             cancelled = true;
473                             return(false);
474                         }
475                         else
476                         {
477 							error = true; // An error has occurred
478 						}
479                     }
480                     if (nread == 0)
481                     {
482                         break;
483                     }
484 
485                     // Force timeout checking for progress dialog
486                     checkTimeout();
487 
488                     // Set percentage value for progress dialog
489                     dataread += nread;
490                     totaldata += nread;
491 
492                     if (progressbar)
493                     {
494                         // Percentage
495                         int pct = (100.0*dataread)/size;
496                         progressbar->setProgress(pct);
497 
498                         // Total data copied
499                         FXString hsize;
500                         char     size[64];
501 
502 #if __WORDSIZE == 64
503                         snprintf(size, sizeof(size)-1, "%ld", totaldata);
504 #else
505                         snprintf(size, sizeof(size)-1, "%lld", totaldata);
506 #endif
507                         hsize = ::hSize(size);
508 #if __WORDSIZE == 64
509                         snprintf(size, sizeof(size)-1, "%s %s", datatext.text(), hsize.text());
510 #else
511                         snprintf(size, sizeof(size)-1, "%s %s", datatext.text(), hsize.text());
512 #endif
513 
514                         datalabel->setText(size);
515                     }
516 
517                     // Give cancel button an opportunity to be clicked
518                     if (cancelButton)
519                     {
520                         getApp()->runModalWhileEvents(cancelButton);
521                     }
522 
523                     // Set labels for progress dialog
524                     FXString label = _("Source: ")+source;
525                     if (uplabel)
526                     {
527                     	uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
528 					}
529                     label = _("Target: ")+target;
530                     if (downlabel)
531                     {
532                     	downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
533 					}
534                     getApp()->repaint();
535 
536                     // If cancel button was clicked, close files and return
537                     if (cancelled)
538                     {
539                         ::close(dst);
540                         ::close(src);
541                         return(false);
542                     }
543                     errno = 0;
544                     nwritten = File::fullwrite(dst, buffer, nread);
545                     errcode = errno;
546                     if (nwritten < 0)
547                     {
548                         forceTimeout();
549 
550                         FXString str;
551                         if (errcode)
552                         {
553                             str.format(_("Can't copy file %s: %s"), target.text(), strerror(errcode));
554                         }
555                         else
556                         {
557                             str.format(_("Can't copy file %s"), target.text());
558                         }
559                         mbox->setText(str);
560                         FXuint answer = mbox->execute(PLACEMENT_OWNER);
561 
562                         restartTimeout();
563                         if (answer == BOX_CLICKED_CANCEL)
564                         {
565                             ::close(dst);
566                             ::close(src);
567                             cancelled = true;
568                             return(false);
569                         }
570                         else
571                         {
572 							error = true; // An error has occurred
573 						}
574                     }
575                 }
576 
577 				// An error has occurred during the copy
578                 if (error)
579                 {
580                 	ok = -2;
581 				}
582                 else
583                 {
584                 	ok = true;
585 				}
586 
587                 ::close(dst);
588 
589                 // Keep original date if asked
590                 if (preserve_date)
591                 {
592                     timbuf.actime = info.st_atime;
593                     timbuf.modtime = info.st_mtime;
594                     errno = 0;
595                     int rc = utime(destfile.text(), &timbuf);
596                     int errcode = errno;
597                     if (warn && rc)
598                     {
599                         forceTimeout();
600 
601                         FXString str;
602                         if (errcode)
603                         {
604                             str.format(_("Can't preserve date when copying file %s : %s"), target.text(), strerror(errcode));
605                         }
606                         else
607                         {
608                             str.format(_("Can't preserve date when copying file %s"), target.text());
609                         }
610                         mbox->setText(str);
611                         FXuint answer = mbox->execute(PLACEMENT_OWNER);
612 
613                         restartTimeout();
614                         if (answer == BOX_CLICKED_CANCEL)
615                         {
616                             ::close(src);
617                             cancelled = true;
618                             return(false);
619                         }
620                     }
621                 }
622             }
623 
624 #if defined(linux)
625             // If source file is on a ISO9660 file system (CD or DVD, thus read-only)
626             // then add to the target the write permission for the user
627             if (ok)
628             {
629                 struct statfs fs;
630                 if ((statfs(source.text(), &fs) == 0) && (fs.f_type == 0x9660))
631                 {
632                     ::chmod(target.text(), info.st_mode|S_IWUSR);
633                 }
634             }
635 #endif
636         }
637         ::close(src);
638     }
639 
640     // File cannot be opened
641     else
642     {
643         forceTimeout();
644         int errcode = errno;
645 
646         FXString str;
647         if (errcode)
648         {
649             str.format(_("Can't copy file %s: %s"), target.text(), strerror(errcode));
650         }
651         else
652         {
653             str.format(_("Can't copy file %s"), target.text());
654         }
655         mbox->setText(str);
656         FXuint answer = mbox->execute(PLACEMENT_OWNER);
657 
658         restartTimeout();
659         if (answer == BOX_CLICKED_CANCEL)
660         {
661             cancelled = true;
662             return(false);
663         }
664         ok = -1; // Prevent displaying an error message
665                  // in the calling function
666     }
667     return(ok);
668 }
669 
670 
671 // Copy directory
copydir(const FXString & source,const FXString & target,struct stat & parentinfo,inodelist * inodes,const FXbool preserve_date)672 int File::copydir(const FXString& source, const FXString& target, struct stat& parentinfo, inodelist* inodes, const FXbool preserve_date)
673 {
674     DIR*           dirp;
675     struct dirent* dp;
676     struct stat    linfo;
677     struct utimbuf timbuf;
678     inodelist*     in, inode;
679     FXString       destfile, oldchild, newchild;
680 
681     FXbool warn = getApp()->reg().readUnsignedEntry("OPTIONS", "preserve_date_warn", true);
682 
683     // Destination file
684     destfile = target;
685 
686     // See if visited this inode already
687     for (in = inodes; in; in = in->next)
688     {
689         if (in->st_ino == parentinfo.st_ino)
690         {
691             return(true);
692         }
693     }
694 
695     // Try make directory, if none exists yet
696     if ((mkdir(destfile.text(), parentinfo.st_mode|S_IWUSR) != 0) && (errno != EEXIST))
697     {
698         return(false);
699     }
700 
701     // Can we stat it
702     if ((lstatrep(destfile.text(), &linfo) != 0) || !S_ISDIR(linfo.st_mode))
703     {
704         return(false);
705     }
706 
707     // Try open directory to copy
708     dirp = opendir(source.text());
709     if (!dirp)
710     {
711         return(false);
712     }
713 
714     // Add this to the list
715     inode.st_ino = linfo.st_ino;
716     inode.next = inodes;
717 
718     // Copy stuff
719     while ((dp = readdir(dirp)) != NULL)
720     {
721         if ((dp->d_name[0] != '.') || ((dp->d_name[1] != '\0') && ((dp->d_name[1] != '.') || (dp->d_name[2] != '\0'))))
722         {
723             oldchild = source;
724             if (!ISPATHSEP(oldchild[oldchild.length()-1]))
725             {
726                 oldchild.append(PATHSEP);
727             }
728             oldchild.append(dp->d_name);
729             newchild = destfile;
730             if (!ISPATHSEP(newchild[newchild.length()-1]))
731             {
732                 newchild.append(PATHSEP);
733             }
734             newchild.append(dp->d_name);
735             if (!copyrec(oldchild, newchild, &inode, preserve_date))
736             {
737                 // If the cancel button was pressed
738                 if (cancelled)
739                 {
740                     closedir(dirp);
741                     return(false);
742                 }
743 
744                 // Or a permission problem occured
745                 else
746                 {
747                     FXString str;
748                     if (::isDirectory(oldchild))
749                     {
750                         str.format(_("Can't copy folder %s : Permission denied"), oldchild.text());
751                     }
752                     else
753                     {
754                         str.format(_("Can't copy file %s : Permission denied"), oldchild.text());
755                     }
756                     forceTimeout();
757                     mbox->setText(str);
758                     FXuint answer = mbox->execute(PLACEMENT_OWNER);
759 
760                     restartTimeout();
761                     if (answer == BOX_CLICKED_CANCEL)
762                     {
763                         closedir(dirp);
764                         cancelled = true;
765                         return(false);
766                     }
767                 }
768             }
769         }
770     }
771 
772     // Close directory
773     closedir(dirp);
774 
775     // Keep original date if asked
776     if (preserve_date)
777     {
778         if (lstatrep(source.text(), &linfo) == 0)
779         {
780             timbuf.actime = linfo.st_atime;
781             timbuf.modtime = linfo.st_mtime;
782             errno = 0;
783             int rc = utime(destfile.text(), &timbuf);
784             int errcode = errno;
785             if (warn && rc)
786             {
787                 forceTimeout();
788                 FXString str;
789                 if (errcode)
790                 {
791                     str.format(_("Can't preserve date when copying folder %s: %s"), target.text(), strerror(errcode));
792                 }
793                 else
794                 {
795                     str.format(_("Can't preserve date when copying folder %s"), target.text());
796                 }
797                 mbox->setText(str);
798                 FXuint answer = mbox->execute(PLACEMENT_OWNER);
799 
800                 restartTimeout();
801                 if (answer == BOX_CLICKED_CANCEL)
802                 {
803                     cancelled = true;
804                     return(false);
805                 }
806             }
807         }
808     }
809 
810     // Success
811     return(true);
812 }
813 
814 
815 // Recursive copy
copyrec(const FXString & source,const FXString & target,inodelist * inodes,const FXbool preserve_date)816 int File::copyrec(const FXString& source, const FXString& target, inodelist* inodes, const FXbool preserve_date)
817 {
818     struct stat linfo1, linfo2;
819 
820     // Source file or directory does not exist
821     if (lstatrep(source.text(), &linfo1) != 0)
822     {
823         return(false);
824     }
825 
826     // If target is not a directory, remove it if allowed
827     if (lstatrep(target.text(), &linfo2) == 0)
828     {
829         if (!S_ISDIR(linfo2.st_mode))
830         {
831             if (!(overwrite|overwrite_all))
832             {
833                 return(false);
834             }
835             if (::unlink(target.text()) != 0)
836             {
837                 return(false);
838             }
839         }
840     }
841 
842     // Source is directory: copy recursively
843     if (S_ISDIR(linfo1.st_mode))
844     {
845         return(File::copydir(source, target, linfo1, inodes, preserve_date));
846     }
847 
848     // Source is regular file: copy block by block
849     if (S_ISREG(linfo1.st_mode))
850     {
851         return(File::copyfile(source, target, preserve_date));
852     }
853 
854     // Remove target if it already exists
855     if (existFile(target))
856     {
857         int ret = File::remove(target);
858         if (!ret)
859         {
860             return(false);
861         }
862     }
863 
864     // Source is fifo: make a new one
865     if (S_ISFIFO(linfo1.st_mode))
866     {
867         return(mkfifo(target.text(), linfo1.st_mode));
868     }
869 
870     // Source is device: make a new one
871     if (S_ISBLK(linfo1.st_mode) || S_ISCHR(linfo1.st_mode) || S_ISSOCK(linfo1.st_mode))
872     {
873         return(mknod(target.text(), linfo1.st_mode, linfo1.st_rdev) == 0);
874     }
875 
876     // Source is symbolic link: make a new one
877     if (S_ISLNK(linfo1.st_mode))
878     {
879         FXString lnkfile = ::readLink(source);
880         return(::symlink(lnkfile.text(), target.text()) == 0);
881     }
882 
883     // This shouldn't happen
884     return(false);
885 }
886 
887 
888 // Copy file (with progress dialog)
889 // Return  0 to allow displaying an error message in the calling function
890 // Return -1 to prevent displaying an error message in the calling function
copy(const FXString & source,const FXString & target,const FXbool confirm_dialog,const FXbool preserve_date)891 int File::copy(const FXString& source, const FXString& target, const FXbool confirm_dialog, const FXbool preserve_date)
892 {
893     FXString targetfile;
894 
895     // Source doesn't exist
896     if (!existFile(source))
897     {
898         forceTimeout();
899         MessageBox::error(this, BOX_OK, _("Error"), _("Source %s doesn't exist"), source.text());
900         return(-1);
901     }
902 
903     // Source and target are identical
904     if (::identical(target, source))
905     {
906         forceTimeout();
907         MessageBox::error(this, BOX_OK, _("Error"), _("Destination %s is identical to source"), target.text());
908         return(-1);
909     }
910 
911     // Source path is included into target path
912     FXString str = source + PATHSEPSTRING;
913     if (target.left(str.length()) == str)
914     {
915         forceTimeout();
916         MessageBox::error(this, BOX_OK, _("Error"), _("Target %s is a sub-folder of source"), target.text());
917         return(-1);
918     }
919 
920     // Target is an existing directory
921     if (::isDirectory(target))
922     {
923         targetfile = target+PATHSEPSTRING+FXPath::name(source);
924     }
925     else
926     {
927         targetfile = target;
928     }
929 
930     // Source and target are identical => add a suffix to the name
931     if (::identical(source, targetfile))
932     {
933         FXString pathname = ::cleanPath(targetfile);
934         targetfile = ::buildCopyName(pathname, ::isDirectory(pathname)); // Remove trailing / if any
935     }
936     // Source and target file are identical
937     if (::identical(targetfile, source))
938     {
939         forceTimeout();
940         MessageBox::error(this, BOX_OK, _("Error"), _("Destination %s is identical to source"), targetfile.text());
941         return(-1);
942     }
943 
944     // Target already exists
945     if (existFile(targetfile))
946     {
947         // Overwrite dialog if necessary
948         if ( (!(overwrite_all | skip_all)) & confirm_dialog )
949         {
950             FXString label = _("Source: ")+source;
951             if (uplabel)
952             {
953 				uplabel->setText(::multiLines(label, MAX_MESSAGE_LENGTH));
954 			}
955             label = _("Target: ")+targetfile;
956             if (downlabel)
957             {
958 				downlabel->setText(::multiLines(label, MAX_MESSAGE_LENGTH));
959 			}
960             getApp()->repaint();
961             forceTimeout();
962             FXuint answer = getOverwriteAnswer(source, targetfile);
963             switch (answer)
964             {
965             // Cancel
966             case 0:
967                 forceTimeout();
968                 cancelled = true;
969                 return(false);
970 
971                 break;
972 
973             // Overwrite
974             case 1:
975                 overwrite = true;
976                 break;
977 
978             // Overwrite all
979             case 2:
980                 overwrite_all = true;
981                 break;
982 
983             // Skip
984             case 3:
985                 overwrite = false;
986                 break;
987 
988             // Skip all
989             case 4:
990                 skip_all = true;
991                 break;
992             }
993         }
994         if ( (!(overwrite | overwrite_all)) | skip_all )
995         {
996             return(true);
997         }
998 
999         // Remove targetfile if source is not a directory
1000         if (!::isDirectory(source))
1001         {
1002             if (File::remove(targetfile) == false)
1003             {
1004                 forceTimeout();
1005                 return(false);
1006             }
1007         }
1008     }
1009 
1010     // Copy file or directory
1011     return(File::copyrec(source, targetfile, NULL, preserve_date));
1012 }
1013 
1014 
1015 // Remove file or directory (with progress dialog)
1016 // Return  0 to allow displaying an error message in the calling function
1017 // Return -1 to prevent displaying an error message in the calling function
remove(const FXString & file)1018 int File::remove(const FXString& file)
1019 {
1020     FXString      dirname;
1021     struct stat   linfo;
1022     static FXbool ISDIR = false;  // Caution! ISDIR is common to all File instances, is that we want?
1023 
1024     if (lstatrep(file.text(), &linfo) == 0)
1025     {
1026         // It is a directory
1027         if (S_ISDIR(linfo.st_mode))
1028         {
1029             DIR* dirp = opendir(file.text());
1030             if (dirp)
1031             {
1032                 struct dirent* dp;
1033                 FXString       child;
1034 
1035                 // Used to display only one progress dialog when deleting a directory
1036                 ISDIR = true;
1037 
1038                 // Force timeout checking for progress dialog
1039                 checkTimeout();
1040 
1041                 // Give cancel button an opportunity to be clicked
1042                 if (cancelButton)
1043                 {
1044                     getApp()->runModalWhileEvents(cancelButton);
1045                 }
1046 
1047                 // Set labels for progress dialog
1048                 FXString label = _("Delete folder: ")+file;
1049                 if (uplabel)
1050                 {
1051                     uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1052                 }
1053                 dirname = FXPath::directory(FXPath::absolute(file));
1054                 label = _("From: ")+dirname;
1055                 if (downlabel)
1056                 {
1057                     downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1058                 }
1059                 getApp()->repaint();
1060 
1061                 // If cancel button was clicked, return
1062                 if (cancelled)
1063                 {
1064                     closedir(dirp);
1065                     return(false);
1066                 }
1067 
1068                 while ((dp = readdir(dirp)) != NULL)
1069                 {
1070                     if ((dp->d_name[0] != '.') || ((dp->d_name[1] != '\0') && ((dp->d_name[1] != '.') || (dp->d_name[2] != '\0'))))
1071                     {
1072                         child = file;
1073                         if (!ISPATHSEP(child[child.length()-1]))
1074                         {
1075                             child.append(PATHSEP);
1076                         }
1077                         child.append(dp->d_name);
1078                         if (!File::remove(child))
1079                         {
1080                             closedir(dirp);
1081                             return(false);
1082                         }
1083                     }
1084                 }
1085                 closedir(dirp);
1086             }
1087             if (rmdir(file.text()) == -1)
1088             {
1089                 int errcode = errno;
1090                 forceTimeout();
1091 
1092                 FXString str;
1093                 if (errcode)
1094                 {
1095                     str.format(_("Can't delete folder %s: %s"), file.text(), strerror(errcode));
1096                 }
1097                 else
1098                 {
1099                     str.format(_("Can't delete folder %s"), file.text());
1100                 }
1101                 mbox->setText(str);
1102                 FXuint answer = mbox->execute(PLACEMENT_OWNER);
1103 
1104                 restartTimeout();
1105                 if (answer == BOX_CLICKED_CANCEL)
1106                 {
1107                     cancelled = true;
1108                     return(false);
1109                 }
1110                 return(-1); // To prevent displaying an error message
1111                 // in the calling function
1112             }
1113             else
1114             {
1115                 return(true);
1116             }
1117         }
1118         else
1119         {
1120             // If it was not a directory
1121             if (!ISDIR)
1122             {
1123                 // Force timeout checking for progress dialog
1124                 checkTimeout();
1125 
1126                 // Give cancel button an opportunity to be clicked
1127                 if (cancelButton)
1128                 {
1129                     getApp()->runModalWhileEvents(cancelButton);
1130                 }
1131 
1132                 // Set labels for progress dialog
1133                 FXString label = _("Delete:")+file;
1134                 if (uplabel)
1135                 {
1136                     uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1137                 }
1138                 dirname = FXPath::directory(FXPath::absolute(file));
1139                 label = _("From: ")+dirname;
1140                 if (downlabel)
1141                 {
1142                     downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1143                 }
1144                 getApp()->repaint();
1145 
1146                 // If cancel button was clicked, return
1147                 if (cancelled)
1148                 {
1149                     return(false);
1150                 }
1151             }
1152             if (::unlink(file.text()) == -1)
1153             {
1154                 int errcode = errno;
1155                 forceTimeout();
1156 
1157                 FXString str;
1158                 if (errcode)
1159                 {
1160                     str.format(_("Can't delete file %s: %s"), file.text(), strerror(errcode));
1161                 }
1162                 else
1163                 {
1164                     str.format(_("Can't delete file %s"), file.text());
1165                 }
1166                 mbox->setText(str);
1167                 FXuint answer = mbox->execute(PLACEMENT_OWNER);
1168 
1169                 restartTimeout();
1170                 if (answer == BOX_CLICKED_CANCEL)
1171                 {
1172                     cancelled = true;
1173                     return(false);
1174                 }
1175                 return(-1); // To prevent displaying an error message
1176                             // in the calling function
1177             }
1178             else
1179             {
1180                 return(true);
1181             }
1182         }
1183     }
1184     return(-1);
1185 }
1186 
1187 
1188 // Rename a file or a directory (no progress dialog)
1189 // Return  0 to allow displaying an error message in the calling function
1190 // Return -1 to prevent displaying an error message in the calling function
rename(const FXString & source,const FXString & target)1191 int File::rename(const FXString& source, const FXString& target)
1192 {
1193     // Source doesn't exist
1194     if (!existFile(source))
1195     {
1196         MessageBox::error(this, BOX_OK, _("Error"), _("Source %s doesn't exist"), source.text());
1197         return(-1);
1198     }
1199 
1200     // Source and target are identical
1201     if (::identical(target, source))
1202     {
1203         MessageBox::error(this, BOX_OK, _("Error"), _("Destination %s is identical to source"), target.text());
1204         return(-1);
1205     }
1206 
1207     // Target already exists => only allow overwriting destination if both source and target are files
1208     if (existFile(target))
1209     {
1210 		// Source or target are a directory
1211 		if (::isDirectory(source) || ::isDirectory(target))
1212 		{
1213 	        MessageBox::error(this, BOX_OK, _("Error"), _("Destination %s already exists"), target.text());
1214 			return(-1);
1215 		}
1216 
1217 		// Source and target are files
1218 		else
1219 		{
1220 			FXuint answer = getOverwriteAnswer(source, target);
1221 			if (answer == 0)
1222 			{
1223 				return(-1);
1224 			}
1225 		}
1226     }
1227 
1228     // Rename file using the standard C function
1229     // This should only work for files that are on the same file system
1230     if (::rename(source.text(), target.text()) == 0)
1231     {
1232         return(true);
1233     }
1234 
1235     int errcode = errno;
1236     if ((errcode != EXDEV) && (errcode != ENOTEMPTY))
1237     {
1238         forceTimeout();
1239         MessageBox::error(this, BOX_OK, _("Error"), _("Can't rename to target %s: %s"), target.text(), strerror(errcode));
1240         return(-1);
1241     }
1242 
1243     // If files are on different file systems, use the copy/delete scheme and preserve the original date
1244     int ret = this->copy(source, target, false, true);
1245     if (ret == true)
1246     {
1247         return(remove(source.text()) == true);
1248     }
1249     else
1250     {
1251         return(false);
1252     }
1253 }
1254 
1255 
1256 // Move files
1257 // Return  0 to allow displaying an error message in the calling function
1258 // Return -1 to prevent displaying an error message in the calling function
move(const FXString & source,const FXString & target,const FXbool restore)1259 int File::move(const FXString& source, const FXString& target, const FXbool restore)
1260 {
1261     // Source doesn't exist
1262     if (!existFile(source))
1263     {
1264         forceTimeout();
1265         MessageBox::error(this, BOX_OK, _("Error"), _("Source %s doesn't exist"), source.text());
1266         return(-1);
1267     }
1268 
1269     // Source and target are identical
1270     if (identical(target, source))
1271     {
1272         forceTimeout();
1273         MessageBox::error(this, BOX_OK, _("Error"), _("Destination %s is identical to source"), target.text());
1274         return(-1);
1275     }
1276 
1277     // Source path is included into target path
1278     FXString str = source + PATHSEPSTRING;
1279     if (target.left(str.length()) == str)
1280     {
1281         forceTimeout();
1282         MessageBox::error(this, BOX_OK, _("Error"), _("Target %s is a sub-folder of source"), target.text());
1283         return(-1);
1284     }
1285 
1286     // Target is an existing directory (don't do this in the restore case)
1287     FXString targetfile;
1288     if (!restore && ::isDirectory(target))
1289     {
1290         targetfile = target+PATHSEPSTRING+FXPath::name(source);
1291     }
1292     else
1293     {
1294         targetfile = target;
1295     }
1296 
1297     // Source and target file are identical
1298     if (::identical(targetfile, source))
1299     {
1300         forceTimeout();
1301         MessageBox::error(this, BOX_OK, _("Error"), _("Destination %s is identical to source"), targetfile.text());
1302         return(-1);
1303     }
1304 
1305     // Force timeout checking for progress dialog
1306     checkTimeout();
1307 
1308     // Give cancel button an opportunity to be clicked
1309     if (cancelButton)
1310     {
1311         getApp()->runModalWhileEvents(cancelButton);
1312     }
1313 
1314     // Set labels for progress dialog
1315     FXString label = _("Source: ")+source;
1316     if (uplabel)
1317     {
1318     	uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1319 	}
1320     label = _("Target: ")+target;
1321     if (downlabel)
1322     {
1323 		downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1324 	}
1325     getApp()->repaint();
1326 
1327     // Target file already exists
1328     if (existFile(targetfile))
1329     {
1330         // Overwrite dialog if necessary
1331         if (!overwrite_all & !skip_all)
1332         {
1333             forceTimeout();
1334             FXuint answer = getOverwriteAnswer(source, targetfile);
1335             restartTimeout();
1336             switch (answer)
1337             {
1338             // Cancel
1339             case 0:
1340                 forceTimeout();
1341                 cancelled = true;
1342                 return(false);
1343 
1344                 break;
1345 
1346             // Overwrite
1347             case 1:
1348                 overwrite = true;
1349                 break;
1350 
1351             // Overwrite all
1352             case 2:
1353                 overwrite_all = true;
1354                 break;
1355 
1356             // Skip
1357             case 3:
1358                 overwrite = false;
1359                 break;
1360 
1361             // Skip all
1362             case 4:
1363                 skip_all = true;
1364                 break;
1365             }
1366         }
1367         if ( (!(overwrite | overwrite_all)) | skip_all )
1368         {
1369             // Hide progress dialog and restart timer
1370             forceTimeout();
1371             restartTimeout();
1372 
1373             return(true);
1374         }
1375     }
1376 
1377     // Get the size of the source
1378     FXulong srcsize = 0;
1379     struct stat linfo;
1380     if (lstatrep(source.text(), &linfo) == 0)
1381     {
1382         char buf[MAXPATHLEN];
1383         if (S_ISDIR(linfo.st_mode)) // Folder
1384         {
1385             FXuint nbfiles = 0, nbsubfolders = 0;
1386             FXulong totalsize=0;
1387             strlcpy(buf, source.text(), source.length()+1);
1388             srcsize = pathsize(buf, &nbfiles, &nbsubfolders,&totalsize);
1389             totaldata += srcsize;
1390         }
1391         else // File
1392         {
1393             srcsize = (FXulong)linfo.st_size;
1394             totaldata += srcsize;
1395         }
1396     }
1397 
1398     if (progressbar)
1399     {
1400         // Trick to display a percentage
1401         int pct = (100.0*rand())/RAND_MAX+50;
1402         progressbar->setProgress((int)pct);
1403 
1404         // Total data moved
1405         FXString hsize;
1406         char     size[64];
1407 
1408 #if __WORDSIZE == 64
1409         snprintf(size, sizeof(size)-1, "%ld", totaldata);
1410 #else
1411         snprintf(size, sizeof(size)-1, "%lld", totaldata);
1412 #endif
1413         hsize = ::hSize(size);
1414 #if __WORDSIZE == 64
1415         snprintf(size, sizeof(size)-1, "%s %s", datatext.text(), hsize.text());
1416 #else
1417         snprintf(size, sizeof(size)-1, "%s %s", datatext.text(), hsize.text());
1418 #endif
1419 
1420         datalabel->setText(size);
1421     }
1422 
1423     // Rename file using the standard C function
1424     // This should only work for files that are on the same file system
1425     if (::rename(source.text(), targetfile.text()) == 0)
1426     {
1427         return(true);
1428     }
1429 
1430     int errcode = errno;
1431     if ((errcode != EXDEV) && (errcode != ENOTEMPTY))
1432     {
1433         forceTimeout();
1434         MessageBox::error(this, BOX_OK, _("Error"), _("Can't rename to target %s: %s"), targetfile.text(), strerror(errcode));
1435         return(-1);
1436     }
1437 
1438     // If files are on different file systems, use the copy/delete scheme and preserve the original date
1439     totaldata -= srcsize; // Avoid counting data twice
1440 
1441     int ret = this->copy(source, target, false, true);
1442 
1443     // Success
1444     if (ret == true)
1445     {
1446         return(remove(source.text()) == true);
1447     }
1448 
1449     // Error during copy
1450     else if (ret == -2)
1451     {
1452 		return true;
1453 	}
1454 
1455     // Operation cancelled
1456     else
1457     {
1458         return(false);
1459     }
1460 }
1461 
1462 
1463 // Symbolic Link file (no progress dialog)
1464 // Return  0 to allow displaying an error message in the calling function
1465 // Return -1 to prevent displaying an error message in the calling function
symlink(const FXString & source,const FXString & target)1466 int File::symlink(const FXString& source, const FXString& target)
1467 {
1468     // Source doesn't exist
1469     if (!existFile(source))
1470     {
1471         forceTimeout();
1472         MessageBox::error(this, BOX_OK, _("Error"), _("Source %s doesn't exist"), source.text());
1473         return(-1);
1474     }
1475 
1476     // Source and target are identical
1477     if (::identical(target, source))
1478     {
1479         forceTimeout();
1480         MessageBox::error(this, BOX_OK, _("Error"), _("Destination %s is identical to source"), target.text());
1481         return(-1);
1482     }
1483 
1484     // Target is an existing directory
1485     FXString targetfile;
1486     if (::isDirectory(target))
1487     {
1488         targetfile = target+PATHSEPSTRING+FXPath::name(source);
1489     }
1490     else
1491     {
1492         targetfile = target;
1493     }
1494 
1495     // Source and target are identical
1496     if (::identical(targetfile, source))
1497     {
1498         forceTimeout();
1499         MessageBox::error(this, BOX_OK, _("Error"), _("Destination %s is identical to source"), targetfile.text());
1500         return(-1);
1501     }
1502 
1503     // Target already exists
1504     if (existFile(targetfile))
1505     {
1506         // Overwrite dialog if necessary
1507         if (!(overwrite_all | skip_all))
1508         {
1509             FXuint answer = getOverwriteAnswer(source, targetfile);
1510             switch (answer)
1511             {
1512             // Cancel
1513             case 0:
1514                 forceTimeout();
1515                 return(false);
1516 
1517                 break;
1518 
1519             // Overwrite
1520             case 1:
1521                 overwrite = true;
1522                 break;
1523 
1524             // Overwrite all
1525             case 2:
1526                 overwrite_all = true;
1527                 break;
1528 
1529             // Skip
1530             case 3:
1531                 overwrite = false;
1532                 break;
1533 
1534             // Skip all
1535             case 4:
1536                 skip_all = true;
1537                 break;
1538             }
1539         }
1540         if ( (!(overwrite | overwrite_all)) | skip_all )
1541         {
1542             return(true);
1543         }
1544     }
1545 
1546     // Create symbolic link using the standard C function
1547     errno = 0;
1548 
1549     // Use the relative path for the symbolic link
1550     FXString relativepath;
1551     if (existFile(target) && ::isDirectory(target))
1552     {
1553         relativepath = FXPath::relative(target, source);
1554     }
1555     else
1556     {
1557         relativepath = FXPath::relative(FXPath::directory(target), source);
1558     }
1559 
1560     int ret = ::symlink(relativepath.text(), targetfile.text());
1561 
1562     int errcode = errno;
1563     if (ret == 0)
1564     {
1565         return(true);
1566     }
1567     else
1568     {
1569         forceTimeout();
1570         if (errcode)
1571         {
1572             MessageBox::error(this, BOX_OK, _("Error"), _("Can't symlink %s: %s"), target.text(), strerror(errcode));
1573         }
1574         else
1575         {
1576             MessageBox::error(this, BOX_OK, _("Error"), _("Can't symlink %s"), target.text());
1577         }
1578         return(-1);
1579     }
1580 }
1581 
1582 
1583 // Chmod a file or directory, recursively or not
1584 // We don't process symbolic links (since their permissions cannot be changed)
1585 //
1586 // Note : the variable file returns the last processed file
1587 // It can be different from the initial path, if recursive chmod is used
1588 // (Used to fill an error message, if needed)
chmod(char * path,char * file,mode_t mode,FXbool rec,const FXbool dironly,const FXbool fileonly)1589 int File::chmod(char* path, char* file, mode_t mode, FXbool rec, const FXbool dironly, const FXbool fileonly)
1590 {
1591     struct stat linfo;
1592 
1593     // Initialise the file variable with the initial path
1594     strlcpy(file, path, strlen(path)+1);
1595 
1596     // If it doesn't exist
1597     if (lstatrep(path, &linfo))
1598     {
1599         return(-1);
1600     }
1601 
1602     // If it's a symbolic link
1603     if (S_ISLNK(linfo.st_mode))
1604     {
1605         return(0);
1606     }
1607 
1608     if (!S_ISDIR(linfo.st_mode)) // File
1609     {
1610         if (dironly)
1611         {
1612             return(0);
1613         }
1614 
1615         // Force timeout checking for progress dialog
1616         checkTimeout();
1617 
1618         // Give cancel button an opportunity to be clicked
1619         if (cancelButton)
1620         {
1621             getApp()->runModalWhileEvents(cancelButton);
1622         }
1623 
1624         // Set labels for progress dialog
1625         FXString label = _("Changing permissions...");
1626         uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1627         label = _("File:")+FXString(path);
1628         downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1629         getApp()->repaint();
1630 
1631         // If cancel button was clicked, return
1632         if (cancelled)
1633         {
1634             return(-1);
1635         }
1636 
1637         return(::chmod(path, mode));
1638     }
1639     else // Directory
1640     {
1641         if ((rec == false) && !fileonly)
1642         {
1643             // Force timeout checking for progress dialog
1644             checkTimeout();
1645 
1646             // Give cancel button an opportunity to be clicked
1647             if (cancelButton)
1648             {
1649                 getApp()->runModalWhileEvents(cancelButton);
1650             }
1651 
1652             // Set labels for progress dialog
1653             FXString label = _("Changing permissions...");
1654             uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1655             label = _("Folder: ")+FXString(path);
1656             downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1657             getApp()->repaint();
1658 
1659             // If cancel button was clicked, return
1660             if (cancelled)
1661             {
1662                 return(-1);
1663             }
1664 
1665             if (::chmod(path, mode)) // Do not change recursively
1666             {
1667                 return(-1);
1668             }
1669         }
1670         else
1671         {
1672             return(rchmod(path, file, mode, dironly, fileonly)); // Recursive change
1673         }
1674     }
1675     return(0);
1676 }
1677 
1678 
1679 // Recursive chmod for a directory
1680 // We don't process symbolic links (since their permissions cannot be changed)
rchmod(char * path,char * file,mode_t mode,const FXbool dironly,const FXbool fileonly)1681 int File::rchmod(char* path, char* file, mode_t mode, const FXbool dironly, const FXbool fileonly)
1682 {
1683     struct stat linfo;
1684 
1685     // Initialize the file variable with the initial path
1686     strlcpy(file, path, strlen(path)+1);
1687 
1688     // If it doesn't exist
1689     if (lstatrep(path, &linfo))
1690     {
1691         return(-1);
1692     }
1693 
1694     // If it's a symbolic link
1695     if (S_ISLNK(linfo.st_mode))
1696     {
1697         return(0);
1698     }
1699 
1700     if (!S_ISDIR(linfo.st_mode)) // File
1701     {
1702         if (dironly)
1703         {
1704             return(0);
1705         }
1706 
1707         // Force timeout checking for progress dialog
1708         checkTimeout();
1709 
1710         // Give cancel button an opportunity to be clicked
1711         if (cancelButton)
1712         {
1713             getApp()->runModalWhileEvents(cancelButton);
1714         }
1715 
1716         // Set labels for progress dialog
1717         FXString label = _("Changing permissions...");
1718         uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1719         label = _("File:")+FXString(path);
1720         downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1721         getApp()->repaint();
1722 
1723         // If cancel button was clicked, return
1724         if (cancelled)
1725         {
1726             return(-1);
1727         }
1728 
1729         return(::chmod(path, mode));
1730     }
1731 
1732     DIR*           dir;
1733     struct dirent* entry;
1734     int            i, pl = strlen(path);
1735 
1736     if (!(dir = opendir(path)))
1737     {
1738         return(-1);
1739     }
1740 
1741     for (i = 0; (entry = readdir(dir)); i++)
1742     {
1743         if ((entry->d_name[0] != '.') || ((entry->d_name[1] != '\0') &&
1744                                           ((entry->d_name[1] != '.') ||
1745                                            (entry->d_name[2] != '\0'))))
1746         {
1747             int   pl1 = pl, l = strlen(entry->d_name);
1748             char* path1 = (char*)alloca(pl1+l+2);
1749 
1750             strlcpy(path1, path, strlen(path)+1);
1751             if (path1[pl1-1] != '/')
1752             {
1753                 path1[pl1++] = '/';
1754             }
1755             strlcpy(path1+pl1, entry->d_name, strlen(entry->d_name)+1);
1756 
1757             // Modify the file variable with the new path
1758             strlcpy(file, path1, strlen(path1)+1);
1759             if (rchmod(path1, file, mode, dironly, fileonly))
1760             {
1761                 closedir(dir);
1762                 return(-1);
1763             }
1764         }
1765     }
1766 
1767     if (closedir(dir))
1768     {
1769         return(-1);
1770     }
1771 
1772     if (fileonly)
1773     {
1774         return(0);
1775     }
1776     else
1777     {
1778         return(::chmod(path, mode));
1779     }
1780 }
1781 
1782 
1783 // Chown a file or directory, recursively or not
1784 // We don't follow symbolic links
1785 //
1786 // Note : the variable file returns the last processed file
1787 // It can be different from the initial path, if recursive chmod is used
1788 // (Used to fill an error message, if needed)
chown(char * path,char * file,uid_t uid,gid_t gid,const FXbool rec,const FXbool dironly,const FXbool fileonly)1789 int File::chown(char* path, char* file, uid_t uid, gid_t gid, const FXbool rec, const FXbool dironly, const FXbool fileonly)
1790 {
1791     struct stat linfo;
1792 
1793     // Initialise the file variable with the initial path
1794     strlcpy(file, path, strlen(path)+1);
1795 
1796     // If it doesn't exist
1797     if (lstatrep(path, &linfo))
1798     {
1799         return(-1);
1800     }
1801 
1802     if (!S_ISDIR(linfo.st_mode)) // File
1803     {
1804         if (dironly)
1805         {
1806             return(0);
1807         }
1808 
1809         // Force timeout checking for progress dialog
1810         checkTimeout();
1811 
1812         // Give cancel button an opportunity to be clicked
1813         if (cancelButton)
1814         {
1815             getApp()->runModalWhileEvents(cancelButton);
1816         }
1817 
1818         // Set labels for progress dialog
1819         FXString label = _("Changing owner...");
1820         uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1821         label = _("File:")+FXString(path);
1822         downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1823         getApp()->repaint();
1824 
1825         // If cancel button was clicked, return
1826         if (cancelled)
1827         {
1828             return(-1);
1829         }
1830 
1831         if (::lchown(path, uid, gid))
1832         {
1833             return(-1);
1834         }
1835     }
1836     else // Directory
1837     {
1838         if ((rec == false) && !fileonly)
1839         {
1840             // Force timeout checking for progress dialog
1841             checkTimeout();
1842 
1843             // Give cancel button an opportunity to be clicked
1844             if (cancelButton)
1845             {
1846                 getApp()->runModalWhileEvents(cancelButton);
1847             }
1848 
1849             // Set labels for progress dialog
1850             FXString label = _("Changing owner...");
1851             uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1852             label = _("Folder: ")+FXString(path);
1853             downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1854             getApp()->repaint();
1855 
1856             // If cancel button was clicked, return
1857             if (cancelled)
1858             {
1859                 return(-1);
1860             }
1861 
1862             if (::lchown(path, uid, gid)) // Do not change recursively
1863             {
1864                 return(-1);
1865             }
1866         }
1867         else if (rchown(path, file, uid, gid, dironly, fileonly)) // Recursive change
1868         {
1869             return(-1);
1870         }
1871     }
1872     return(0);
1873 }
1874 
1875 
1876 // Recursive chown for a directory
1877 // We don't follow symbolic links
rchown(char * path,char * file,uid_t uid,gid_t gid,const FXbool dironly,const FXbool fileonly)1878 int File::rchown(char* path, char* file, uid_t uid, gid_t gid, const FXbool dironly, const FXbool fileonly)
1879 {
1880     struct stat linfo;
1881 
1882     // Initialise the file variable with the initial path
1883     strlcpy(file, path, strlen(path)+1);
1884 
1885     // If it doesn't exist
1886     if (lstatrep(path, &linfo))
1887     {
1888         return(-1);
1889     }
1890 
1891     if (!S_ISDIR(linfo.st_mode)) // file
1892     {
1893         if (dironly)
1894         {
1895             return(0);
1896         }
1897 
1898         // Force timeout checking for progress dialog
1899         checkTimeout();
1900 
1901         // Give cancel button an opportunity to be clicked
1902         if (cancelButton)
1903         {
1904             getApp()->runModalWhileEvents(cancelButton);
1905         }
1906 
1907         // Set labels for progress dialog
1908         FXString label = _("Changing owner...");
1909         uplabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1910         label = _("File:")+FXString(path);
1911         downlabel->setText(::truncLine(label, MAX_MESSAGE_LENGTH));
1912         getApp()->repaint();
1913 
1914         // If cancel button was clicked, return
1915         if (cancelled)
1916         {
1917             return(-1);
1918         }
1919 
1920         return(::lchown(path, uid, gid));
1921     }
1922 
1923     DIR*           dir;
1924     struct dirent* entry;
1925     int            i, pl = strlen(path);
1926 
1927     if (!(dir = opendir(path)))
1928     {
1929         return(-1);
1930     }
1931 
1932     for (i = 0; (entry = readdir(dir)); i++)
1933     {
1934         if ((entry->d_name[0] != '.') || ((entry->d_name[1] != '\0') &&
1935                                           ((entry->d_name[1] != '.') ||
1936                                            (entry->d_name[2] != '\0'))))
1937         {
1938             int   pl1 = pl, l = strlen(entry->d_name);
1939             char* path1 = (char*)alloca(pl1+l+2);
1940 
1941             strlcpy(path1, path, strlen(path)+1);
1942             if (path1[pl1-1] != '/')
1943             {
1944                 path1[pl1++] = '/';
1945             }
1946             strlcpy(path1+pl1, entry->d_name, strlen(entry->d_name)+1);
1947             strlcpy(file, path1, strlen(path1)+1);
1948             if (rchown(path1, file, uid, gid, dironly, fileonly))
1949             {
1950                 closedir(dir);
1951                 return(-1);
1952             }
1953         }
1954     }
1955 
1956     if (closedir(dir))
1957     {
1958         return(-1);
1959     }
1960 
1961     if (fileonly)
1962     {
1963         return(0);
1964     }
1965     else
1966     {
1967         return(::lchown(path, uid, gid));
1968     }
1969 }
1970 
1971 
1972 // Extract an archive in a specified directory
extract(const FXString name,const FXString dir,const FXString cmd)1973 int File::extract(const FXString name, const FXString dir, const FXString cmd)
1974 {
1975     int ret;
1976 
1977     // Change to the specified directory
1978     FXString currentdir = FXSystem::getCurrentDirectory();
1979 
1980     ret = chdir(dir.text());
1981     if (ret < 0)
1982     {
1983         int errcode = errno;
1984         if (errcode)
1985         {
1986             MessageBox::error(this, BOX_OK, _("Error"), _("Can't enter folder %s: %s"), dir.text(), strerror(errcode));
1987         }
1988         else
1989         {
1990             MessageBox::error(this, BOX_OK, _("Error"), _("Can't enter folder %s"), dir.text());
1991         }
1992 
1993         return(0);
1994     }
1995 
1996     // Make and show command window
1997     CommandWindow* cmdwin = new CommandWindow(getApp(), _("Extract archive"), cmd, 30, 80);
1998     cmdwin->create();
1999     cmdwin->setIcon(archexticon);
2000 
2001     // The command window object deletes itself after closing the window!
2002 
2003     // Return to initial directory
2004     ret = chdir(currentdir.text());
2005     if (ret < 0)
2006     {
2007         int errcode = errno;
2008         if (errcode)
2009         {
2010             MessageBox::error(this, BOX_OK, _("Error"), _("Can't enter folder %s: %s"), currentdir.text(), strerror(errcode));
2011         }
2012         else
2013         {
2014             MessageBox::error(this, BOX_OK, _("Error"), _("Can't enter folder %s"), currentdir.text());
2015         }
2016 
2017         return(0);
2018     }
2019 
2020     return(1);
2021 }
2022 
2023 
2024 // Create an archive
archive(const FXString name,const FXString cmd)2025 int File::archive(const FXString name, const FXString cmd)
2026 {
2027     // Target file already exists
2028     if (existFile(FXPath::dequote(name)))
2029     {
2030 		FXString msg;
2031 		msg.format(_("File %s already exists.\nOverwrite?"), name.text());
2032 		OverwriteBox* dlg = new OverwriteBox(ownerwin, _("Confirm Overwrite"), msg, OVWBOX_SINGLE_FILE);
2033 		FXuint answer = dlg->execute(PLACEMENT_OWNER);
2034 		delete dlg;
2035 		if (answer == 0)
2036 		{
2037 			return(false);
2038 		}
2039     }
2040 
2041     // Make and show command window
2042     CommandWindow* cmdwin = new CommandWindow(getApp(), _("Add to archive"), cmd, 30, 80);
2043     cmdwin->create();
2044     cmdwin->setIcon(archaddicon);
2045 
2046     // The command window object deletes itself after closing the window!
2047 
2048     return(1);
2049 }
2050 
2051 
2052 #if defined(linux)
mount(const FXString dir,const FXString msg,const FXString cmd,const FXuint op)2053 int File::mount(const FXString dir, const FXString msg, const FXString cmd, const FXuint op)
2054 {
2055     FXbool mount_messages = getApp()->reg().readUnsignedEntry("OPTIONS", "mount_messages", true);
2056 
2057     // Show progress dialog (no timer here)
2058     show(PLACEMENT_OWNER);
2059     getApp()->forceRefresh();
2060     getApp()->flush();
2061 
2062     // Set labels for progress dialog
2063     uplabel->setText(msg);
2064     downlabel->setText(dir.text());
2065     getApp()->repaint();
2066 
2067     // Give cancel button an opportunity to be clicked
2068     if (cancelButton)
2069     {
2070         getApp()->runModalWhileEvents(cancelButton);
2071     }
2072 
2073     // If cancel button was clicked, return
2074     if (cancelled)
2075     {
2076         return(-1);
2077     }
2078 
2079     // Perform the mount/unmount command
2080     FILE* pcmd = popen(cmd.text(), "r");
2081     if (!pcmd)
2082     {
2083         MessageBox::error(this, BOX_OK, _("Error"), _("Failed command: %s"), cmd.text());
2084         return(-1);
2085     }
2086 
2087     // Get error message if any
2088     char text[10000] = { 0 };
2089     FXString buf;
2090     while (fgets(text, sizeof(text), pcmd))
2091     {
2092         buf += text;
2093     }
2094     snprintf(text, sizeof(text)-1, "%s", buf.text());
2095 
2096     // Close the stream
2097     if ((pclose(pcmd) == -1) && (strcmp(text, "") != 0))
2098     {
2099         MessageBox::error(this, BOX_OK, _("Error"), "%s", text);
2100         return(-1);
2101     }
2102 
2103     // Hide progress dialog
2104     hide();
2105 
2106     // Success message, eventually
2107     if (mount_messages)
2108     {
2109         if (op == MOUNT)
2110         {
2111             MessageBox::information(this, BOX_OK, _("Success"), _("Folder %s was successfully mounted."), dir.text());
2112         }
2113         else
2114         {
2115             MessageBox::information(this, BOX_OK, _("Success"), _("Folder %s was successfully unmounted."), dir.text());
2116         }
2117     }
2118     return(1);
2119 }
2120 
2121 
2122 // Install / Upgrade package
pkgInstall(const FXString name,const FXString cmd)2123 int File::pkgInstall(const FXString name, const FXString cmd)
2124 {
2125     // Make and show command window
2126     CommandWindow* cmdwin = new CommandWindow(getApp(), _("Install/Upgrade package"), cmd, 10, 80);
2127 
2128     cmdwin->create();
2129 
2130     FXString msg;
2131     msg.format(_("Installing package: %s \n"), name.text());
2132     cmdwin->appendText(msg.text());
2133 
2134     // The command window object deletes itself after closing the window!
2135 
2136     return(1);
2137 }
2138 
2139 
2140 // Uninstall package
pkgUninstall(const FXString name,const FXString cmd)2141 int File::pkgUninstall(const FXString name, const FXString cmd)
2142 {
2143     // Make and show command window
2144     CommandWindow* cmdwin = new CommandWindow(getApp(), _("Uninstall package"), cmd, 10, 80);
2145 
2146     cmdwin->create();
2147 
2148     FXString msg;
2149     msg.format(_("Uninstalling package: %s \n"), name.text());
2150     cmdwin->appendText(msg.text());
2151 
2152     // The command window object deletes itself after closing the window!
2153 
2154     return(1);
2155 }
2156 
2157 
2158 #endif
2159 
2160 
2161 // Handle cancel button in progress bar dialog
onCmdCancel(FXObject *,FXSelector,void *)2162 long File::onCmdCancel(FXObject*, FXSelector, void*)
2163 {
2164     cancelled = true;
2165     return(1);
2166 }
2167 
2168 
2169 // Handle timeout for progress bar
onTimeout(FXObject *,FXSelector,void *)2170 long File::onTimeout(FXObject*, FXSelector, void*)
2171 {
2172     show(PLACEMENT_OWNER);
2173     getApp()->forceRefresh();
2174     getApp()->flush();
2175     return(1);
2176 }
2177