option to enter new name in overwrite dialog
[case] / src / fileoperator.cpp
1 // case - file manager for N900
2 // Copyright (C) 2010 Lukas Hrazky <lukkash@email.cz>
3 // 
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 // 
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 // 
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
18 #include "fileoperator.h"
19
20 #include <QtGui>
21 #include <QDir>
22 #include <QMessageBox>
23 #include <QHBoxLayout>
24 #include <QChar>
25
26 #include "dialog.h"
27
28 #include <math.h>
29 #include <errno.h>
30 #include <iostream>
31
32
33 #define BLOCK_SIZE 524288
34
35
36 #define SHOW_ERROR_PROMPT(promptString, fileName)                                           \
37     response = FileOperator::NONE;                                                          \
38     if (ignoreAll[errno]) {                                                                 \
39         response = FileOperator::IGNORE;                                                    \
40     } else {                                                                                \
41         char buf[255];                                                                      \
42         char *realBuf = strerror_r(errno, buf, 255);                                        \
43         emit showErrorPrompt(this, promptString + " " + realBuf + ".", fileName, errno);    \
44         waitCond.wait(&mutex);                                                              \
45     }
46
47
48 #define ERROR_PROMPT(operation, promptString, fileName)                                     \
49 {                                                                                           \
50     response = FileOperator::NONE;                                                          \
51     while (!abort && operation) {                                                           \
52         SHOW_ERROR_PROMPT(promptString, fileName)                                           \
53         if (response == FileOperator::IGNORE) {                                             \
54             break;                                                                          \
55         }                                                                                   \
56     }                                                                                       \
57 }
58
59
60 #define SPECIAL_COPY_ERROR_PROMPT(operation, promptString, fileName)                        \
61 {                                                                                           \
62     ERROR_PROMPT(operation, promptString, fileName)                                         \
63     if (abort || response == FileOperator::IGNORE) {                                        \
64         if (!abort) {                                                                       \
65             updateProgress(fileSizeMap[path]);                                              \
66             removeExcludeFiles.insert(path);                                                \
67         }                                                                                   \
68         return;                                                                             \
69     }                                                                                       \
70 }
71
72
73 #define OVERWRITE_PROMPT(file, newFile)                                                     \
74 {                                                                                           \
75     response = FileOperator::NONE;                                                          \
76                                                                                             \
77     while (response == FileOperator::NONE && newFile.exists()) {                            \
78         if (overwriteAll != FileOperator::NONE) {                                           \
79             response = overwriteAll;                                                        \
80         } else {                                                                            \
81             emit showOverwritePrompt(this, newFile.absoluteFilePath(),                      \
82                 newFile.isDir() && file.isDir());                                           \
83             waitCond.wait(&mutex);                                                          \
84                                                                                             \
85             if (response == FileOperator::NONE) {                                           \
86                 emit showInputFilenamePrompt(this, newFile, file.isDir());                  \
87                 waitCond.wait(&mutex);                                                      \
88                 if (newNameFromDialog.size()) {                                             \
89                     newFile.setFile(newNameFromDialog);                                     \
90                 }                                                                           \
91             }                                                                               \
92         }                                                                                   \
93     }                                                                                       \
94     if (response == FileOperator::ASK) response = FileOperator::NONE;                       \
95 }
96
97
98 FileOperator::FileOperator(QWidget *parent) : QWidget(parent) {
99     QHBoxLayout *layout = new QHBoxLayout;
100     layout->setContentsMargins(0, 0, 0, 0);
101     layout->setSpacing(0);
102     setLayout(layout);
103     qRegisterMetaType<QFileInfo>("QFileInfo");
104 }
105
106
107 QString FileOperator::shortenPath(const QString &path) {
108     QString homePath = QFSFileEngine::homePath();
109     QString result = path;
110     if (path.indexOf(homePath, 0) == 0) {
111         result.replace(0, homePath.size(), "~");
112     }
113
114     return result;
115 }
116
117
118 void FileOperator::deleteFiles(const QFileInfoList &files) {
119     QString title, desc;
120     if (files.size() == 1) {
121         title = tr("Delete file");
122         desc = tr("Are you sure you want to delete %1?")
123             .arg(FileOperator::shortenPath(files[0].absoluteFilePath()));
124     } else {
125         title = tr("Delete files");
126         desc = tr("You are about to delete %1 files. Are you sure you want to continue?").arg(files.size());
127     }
128
129     int confirm = QMessageBox::warning(
130         0,
131         title,
132         desc,
133         QMessageBox::Yes,
134         QMessageBox::No
135     );
136
137     if(confirm == QMessageBox::Yes) {
138         caterNewThread(new DeleteThread(files));
139     }
140 }
141
142
143 void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) {
144     QString title, desc;
145     if (files.size() == 1) {
146         title = tr("Copy file");
147         desc = tr("Are you sure you want to copy %1 to %2?")
148             .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
149             .arg(FileOperator::shortenPath(destination.absolutePath()));
150     } else {
151         title = tr("Copy files");
152         desc = tr("You are about to copy %1 files to %2. Are you sure you want to continue?")
153             .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
154     }
155
156     int confirm = QMessageBox::warning(
157         0,
158         title,
159         desc,
160         QMessageBox::Yes,
161         QMessageBox::No
162     );
163
164     if(confirm == QMessageBox::Yes) {
165         caterNewThread(new CopyThread(files, destination));
166     }
167 }
168
169
170 void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) {
171     // for move we don't wanna move to the same dir
172     if (files[0].absolutePath() == destination.absolutePath()) return;
173
174     QString title, desc;
175     if (files.size() == 1) {
176         title = tr("Move file");
177         desc = tr("Are you sure you want to move %1 to %2?")
178             .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
179             .arg(FileOperator::shortenPath(destination.absolutePath()));
180     } else {
181         title = tr("Move files");
182         desc = tr("You are about to move %1 files to %2. Are you sure you want to continue?")
183             .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
184     }
185
186     int confirm = QMessageBox::warning(
187         0,
188         title,
189         desc,
190         QMessageBox::Yes,
191         QMessageBox::No
192     );
193
194     if(confirm == QMessageBox::Yes) {
195         caterNewThread(new MoveThread(files, destination));
196     }
197 }
198
199
200 void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator,
201     const QString &message,
202     const QString &fileName,
203     const int err)
204 {
205     QMessageBox msgBox;
206     msgBox.addButton(QMessageBox::Cancel);
207     QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
208     QAbstractButton *retryButton = msgBox.addButton(QMessageBox::Retry);
209     QAbstractButton *ignoreButton = msgBox.addButton(QMessageBox::Ignore);
210     QAbstractButton *ignoreAllButton = msgBox.addButton(tr("Ignore All"), QMessageBox::AcceptRole);
211     msgBox.setText(message.arg(FileOperator::shortenPath(fileName)));
212
213     msgBox.exec();
214
215     if (msgBox.clickedButton() == abortButton) {
216         manipulator->setResponse(ABORT);
217     } else if (msgBox.clickedButton() == retryButton) {
218         manipulator->setResponse(RETRY);
219     } else if (msgBox.clickedButton() == ignoreButton) {
220         manipulator->setResponse(IGNORE);
221     } else if (msgBox.clickedButton() == ignoreAllButton) {
222         manipulator->setResponse(IGNORE, true, err);
223     }
224 }
225
226
227 void FileOperator::showOverwritePrompt(
228     FileManipulatorThread* manipulator,
229     const QString &fileName,
230     const bool dirOverDir)
231 {
232     Dialog msgBox;
233     msgBox.addButtonFirst(QDialogButtonBox::Cancel);
234     QAbstractButton *yesButton = msgBox.addButtonFirst(QDialogButtonBox::Yes);
235     QAbstractButton *yesToAllButton = msgBox.addButtonFirst(QDialogButtonBox::YesToAll);
236     QAbstractButton *noButton = msgBox.addButtonSecond(QDialogButtonBox::No);
237     QAbstractButton *noToAllButton = msgBox.addButtonSecond(QDialogButtonBox::NoToAll);
238     QAbstractButton *abortButton = msgBox.addButtonSecond(tr("Abort"), QDialogButtonBox::DestructiveRole);
239     QAbstractButton *newNameButton = msgBox.addButtonFirst(tr("New Name"), QDialogButtonBox::AcceptRole);
240     QAbstractButton *askButton = 0;
241     QAbstractButton *skipDirButton = 0;
242
243     if (dirOverDir) {
244         msgBox.setText(tr("Directory %1 already exists. Overwrite the files inside?")
245             .arg(FileOperator::shortenPath(fileName)));
246         askButton = msgBox.addButtonFirst(tr("Ask"), QDialogButtonBox::AcceptRole);
247         skipDirButton = msgBox.addButtonSecond(tr("Skip"), QDialogButtonBox::NoRole);
248     } else {
249         msgBox.setText(tr("File %1 already exists. Overwrite?").arg(FileOperator::shortenPath(fileName)));
250     }
251
252     msgBox.exec();
253
254     if (msgBox.clickedButton == abortButton) {
255         manipulator->setResponse(ABORT);
256     } else if (msgBox.clickedButton == yesButton) {
257         manipulator->setResponse(OVERWRITE);
258     } else if (msgBox.clickedButton == yesToAllButton) {
259         manipulator->setResponse(OVERWRITE, true);
260     } else if (msgBox.clickedButton == noButton) {
261         manipulator->setResponse(KEEP);
262     } else if (msgBox.clickedButton == noToAllButton) {
263         manipulator->setResponse(KEEP, true);
264     } else if (msgBox.clickedButton == askButton) {
265         manipulator->setResponse(ASK);
266     } else if (msgBox.clickedButton == newNameButton) {
267         manipulator->setResponse(NONE);
268     } else if (msgBox.clickedButton == skipDirButton) {
269         manipulator->setResponse(SKIP_DIR);
270     }
271 }
272
273
274 void FileOperator::showInputFilenamePrompt(FileManipulatorThread* manipulator,
275     const QFileInfo &file,
276     const bool dir)
277 {
278     bool ok;
279     QString prompt, error;
280
281     if (dir) {
282         prompt = tr("Enter the new directory name.");
283     } else {
284         prompt = tr("Enter the new file name.");
285     }
286
287     manipulator->mutex.lock();
288
289     manipulator->newNameFromDialog = "";
290     QString text = file.fileName();
291
292     
293     while (true) {
294         text = QInputDialog::getText(this, QString(), prompt + error, QLineEdit::Normal, text, &ok);
295
296         if (!ok) break;
297
298         error = "";
299         if (text.contains(QRegExp("[\"*/:<>?\\\\|]"))) {
300             error = "<small><br/><font color = 'red'>" + tr("The name cannot contain any of the following characters: ") +
301                 "\"*/:&lt;&gt;?\\|</font></small>";
302         } else if (ok && !text.isEmpty()) {
303             QFileInfo info(file.path() + "/" + text);
304             manipulator->newNameFromDialog = info.absoluteFilePath();
305             break;
306         }
307     }
308
309     manipulator->mutex.unlock();
310     manipulator->waitCond.wakeAll();
311 }
312
313
314 void FileOperator::remove(FileManipulatorThread* manipulator) {
315     manipulator->wait();
316     layout()->removeWidget(manipulator->progressBar);
317     manipulatorList.removeAll(manipulator);
318     delete manipulator;
319 }
320
321
322 void FileOperator::setBarSize(FileManipulatorThread* manipulator, unsigned int size) {
323     if (!manipulator->progressBar->maximum()) {
324         manipulator->startTime = time(0);
325     }
326     manipulator->progressBar->setMinimum(0);
327     manipulator->progressBar->setMaximum(size);
328 }
329
330
331 void FileOperator::updateProgress(FileManipulatorThread* manipulator, int value) {
332     manipulator->setText(value);
333 }
334
335
336 void FileOperator::caterNewThread(FileManipulatorThread *thread) {
337     manipulatorList.append(thread);
338
339     connect(thread, SIGNAL(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)),
340         this, SLOT(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)));
341     connect(thread, SIGNAL(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)),
342         this, SLOT(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)));
343     connect(thread, SIGNAL(showInputFilenamePrompt(FileManipulatorThread*, const QFileInfo&, bool)),
344         this, SLOT(showInputFilenamePrompt(FileManipulatorThread*, const QFileInfo&, bool)));
345     connect(thread, SIGNAL(finished(FileManipulatorThread*)),
346         this, SLOT(remove(FileManipulatorThread*)));
347     connect(thread, SIGNAL(setBarSize(FileManipulatorThread*, unsigned int)),
348         this, SLOT(setBarSize(FileManipulatorThread*, unsigned int)));
349     connect(thread, SIGNAL(updateProgress(FileManipulatorThread*, int)),
350         this, SLOT(updateProgress(FileManipulatorThread*, int)));
351
352     layout()->addWidget(thread->progressBar);
353     thread->start(QThread::LowestPriority);
354 }
355
356
357 FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir dest) :
358     progressBar(new QProgressBar()),
359     startTime(0),
360     files(files),
361     dest(dest),
362     response(FileOperator::NONE),
363     overwriteAll(FileOperator::NONE),
364     abort(false),
365     lastTimeUpdate(0),
366     barSize(0),
367     barValue(0),
368     fileSize(0),
369     fileValue(0)
370 {
371     memset(ignoreAll, false, sizeof(ignoreAll));
372     progressBar->setMaximum(1);
373     progressBar->setValue(0);
374     progressBar->setMinimum(0);
375     QFont barFont = progressBar->font();
376     barFont.setPointSize(12);
377     progressBar->setFont(barFont);
378     progressBar->setFormat(tr("Gathering information..."));
379     progressBar->setMinimumHeight(44);
380     progressBar->setStyle(new QPlastiqueStyle);
381     //progressBar->setStyle(new QMotifStyle);
382 }
383
384
385 FileManipulatorThread::~FileManipulatorThread() {
386     if (!abort && progressBar->value() < progressBar->maximum()) {
387         std::cout << "WARNING: deleting a progressbar which's value " << progressBar->value() <<
388             " has not reached maximum of " << progressBar->maximum() << std::endl;
389     }
390     delete progressBar;
391 }
392
393
394 void FileManipulatorThread::setResponse(
395     const FileOperator::Response response,
396     const bool applyToAll,
397     const int err)
398 {
399     mutex.lock();
400
401     this->response = response;
402
403     if (applyToAll) {
404         if (response == FileOperator::KEEP
405             || response == FileOperator::OVERWRITE
406             || response == FileOperator::NONE)
407         {
408             overwriteAll = response;
409         }
410
411         if (response == FileOperator::IGNORE) {
412             ignoreAll[err] = true;
413         }
414     }
415
416     if (response == FileOperator::ABORT) abort = true;
417
418     mutex.unlock();
419     waitCond.wakeAll();
420 }
421
422
423 void FileManipulatorThread::processFiles(const QFileInfoList &files) {
424     for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
425         perform(*it);
426         if (abort) break;
427     }
428 }
429
430
431 bool FileManipulatorThread::remove(QString &fileName, const bool doUpdates) {
432     return remove(QFileInfo(fileName), doUpdates);
433 }
434
435
436 bool FileManipulatorThread::remove(const QFileInfoList &files, const bool doUpdates) {
437     bool res = true;
438     for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
439         if (!remove(*it, doUpdates)) res = false;
440         if (abort) break;
441     }
442     return res;
443 }
444
445
446 bool FileManipulatorThread::remove(const QFileInfo &file, const bool doUpdates) {
447     std::cout << "DELETING " << file.absoluteFilePath().toStdString() << std::endl;
448
449     QString path = file.absoluteFilePath();
450
451     if (removeExcludeFiles.contains(path)) {
452         if (doUpdates) updateProgress(1);
453         return false;
454     }
455
456     QFSFileEngine engine(path);
457
458     if (doUpdates) updateFile(path);
459
460     if (file.isDir()) {
461         if (!remove(listDirFiles(path), doUpdates)) {
462             if (doUpdates) updateProgress(1);
463             return false;
464         }
465
466         if (!listDirFiles(path).size()) {
467             ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
468         }
469     } else {
470         ERROR_PROMPT(!engine.remove(), tr("Error deleting file %1."), path)
471     }
472
473     if (!abort && doUpdates) updateProgress(1);
474
475     if (abort || response == FileOperator::IGNORE) return false;
476     return true;
477 }
478
479
480 void FileManipulatorThread::copy(const QFileInfo &file) {
481     std::cout << "COPYING " << file.absoluteFilePath().toStdString()
482         << " to " << dest.absolutePath().toStdString() << std::endl;
483
484     QString path(file.absoluteFilePath());
485     QFSFileEngine engine(path);
486     QFileInfo newFile(dest.absolutePath() + "/" + file.fileName());
487
488     updateFile(path);
489
490     // hack to prevent asking about the same file if we already asked in the rename(...) function
491     if (overwriteAll == FileOperator::DONT_ASK_ONCE) {
492         overwriteAll = FileOperator::NONE;
493     } else {
494         OVERWRITE_PROMPT(file, newFile)
495     }
496
497     QString newPath(newFile.absoluteFilePath());
498     QFSFileEngine newEngine(newPath);
499
500     if (abort) return;
501
502     if (file.isDir()) {
503         // save the overwrite response, because the response variable will get ovewritten in remove(...)
504         FileOperator::Response overwriteResponse = response;
505
506         if (newFile.exists() && !newFile.isDir()) {
507             // overwriting a file, so check for KEEP and handle it
508             if (response == FileOperator::KEEP) {
509                 updateProgress(fileSizeMap[path]);
510                 removeExcludeFiles.insert(path);
511                 return;
512             }
513
514             // if it should not be kept, remove it and return on failure
515             if(!remove(newPath)) {
516                 updateProgress(fileSizeMap[path]);
517                 return;
518             }
519             // create new info since we deleted the file - is it needed?
520             newFile = QFileInfo(newPath);
521         } else {
522             // overwriting a directory - response KEEP means to keep the files inside,
523             // SKIP_DIR means to skip the dir completely
524             if (response == FileOperator::SKIP_DIR) {
525                 updateProgress(fileSizeMap[path]);
526                 removeExcludeFiles.insert(path);
527                 return;
528             }
529         }
530
531         if (!newFile.exists()) {
532             SPECIAL_COPY_ERROR_PROMPT(!engine.mkdir(newPath, false),
533                 tr("Error creating directory %1."), newPath)
534         }
535
536         // we've done the job with the dir, so update progress and recurse into the dir
537         updateProgress(1);
538         
539         // change the dest for the recursion
540         QDir destBackup = dest;
541         dest = newPath;
542
543         // and set overwriteAll to the response we got a while ago
544         // because it applies to the files inside the dir
545         FileOperator::Response tmpResp = overwriteAll;
546         overwriteAll = overwriteResponse;
547
548         processFiles(listDirFiles(path));
549
550         overwriteAll = tmpResp;
551
552         ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
553             tr("Error setting permissions for directory %1."), newPath)
554
555         if (abort) return;
556
557         dest = destBackup;
558     } else {
559         if (response == FileOperator::KEEP) {
560             updateProgress(fileSizeMap[path]);
561             removeExcludeFiles.insert(path);
562             return;
563         }
564
565         SPECIAL_COPY_ERROR_PROMPT(engine.isSequential(), tr("Cannot copy sequential file %1."), path)
566
567         if (newFile.exists() && newFile.isDir()) {
568             SPECIAL_COPY_ERROR_PROMPT(!remove(newPath),
569                 tr("Cannot replace directory %1 due to previous errors."), newPath)
570         }
571
572         SPECIAL_COPY_ERROR_PROMPT(!engine.open(QIODevice::ReadOnly), tr("Error reading file %1."), path)
573
574         bool ignore = false, newFileWritten = false;
575         while (!abort && !ignore) {
576             engine.seek(0);
577             fileValue = 0;
578
579             ERROR_PROMPT(!newEngine.open(QIODevice::WriteOnly | QIODevice::Truncate),
580                 tr("Error writing file %1."), newPath)
581
582             if (abort || response == FileOperator::IGNORE) {
583                 if (response == FileOperator::IGNORE) {
584                     updateProgress(fileSizeMap[path]);
585                     removeExcludeFiles.insert(path);
586                     ignore = true;
587                 }
588                 break;
589             }
590
591             newFileWritten = true;
592
593             bool error = false;
594             char block[BLOCK_SIZE];
595             qint64 bytes;
596             while ((bytes = engine.read(block, sizeof(block))) > 0) {
597                 if (bytes == -1 || bytes != newEngine.write(block, bytes)) {
598                     if (bytes == -1) {
599                         SHOW_ERROR_PROMPT(tr("Error while reading from file %1."), path);
600                     } else {
601                         SHOW_ERROR_PROMPT(tr("Error while writing to file %1."), newPath);
602                     }
603
604                     if (!abort) {
605                         if (response == FileOperator::IGNORE) {
606                             updateProgress(fileSizeMap[path] - fileValue);
607                             removeExcludeFiles.insert(path);
608                             ignore = true;
609                         } else {
610                             updateProgress(-fileValue);
611                         }
612                     }
613                     error = true;
614                     break;
615                 }
616
617                 updateProgress(1);
618             }
619
620             if (!error) break;
621         }
622
623         engine.close();
624         newEngine.close();
625
626         if (abort || ignore) {
627             if (newFileWritten) {
628                 newEngine.remove();
629             }
630         } else {
631             ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
632                 tr("Error setting permissions for file %1."), newPath)
633         }
634     }
635 }
636
637
638 unsigned int FileManipulatorThread::calculateFileSize(const QFileInfoList &files,
639     const bool count,
640     const bool addSize)
641 {
642     unsigned int res = 0;
643
644     for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
645         unsigned int size = 0;
646
647         if (it->isDir()) {
648             size += calculateFileSize(listDirFiles(it->absoluteFilePath()), count, addSize);
649         }
650
651         if (addSize) {
652             if (it->isDir()) {
653                 ++size;
654             } else {
655                 size += ceil(static_cast<float>(it->size()) / BLOCK_SIZE);
656             }
657             fileSizeMap[it->absoluteFilePath()] = size;
658         }
659
660         if (count) {
661             ++size;
662         }
663
664         res += size;
665     }
666
667     return res;
668 }
669
670
671 QFileInfoList FileManipulatorThread::listDirFiles(const QString &dirPath) {
672     QDir dir = dirPath;
673     return dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden);
674 }
675
676
677 void FileManipulatorThread::setBarSize(unsigned int size) {
678     barSize = size;
679     emit setBarSize(this, size);
680 }
681
682
683 void FileManipulatorThread::updateProgress(int value) {
684     barValue += value;
685     fileValue += value;
686     emit updateProgress(this, value);
687 }
688
689
690 void FileManipulatorThread::updateFile(const QString &name) {
691     fileValue = 0;
692     fileName = FileOperator::shortenPath(name);
693     emit updateProgress(this, 0);
694 }
695
696
697 void FileManipulatorThread::setText(int value) {
698     if (progressBar->value() + value > progressBar->maximum()) {
699         std::cout << "WARNING: exceeding progressbar maximum (" << progressBar->maximum()
700             << ") by " << value << std::endl;
701     }
702  
703     time_t now = time(0);
704     if (lastTimeUpdate < now) {
705         lastTimeUpdate = now;
706
707         time_t elapsed = now - startTime;
708         time_t remaining = (time_t) ((float) elapsed / barValue * (barSize - barValue));
709         struct tm *ts = gmtime(&remaining);
710         
711         if (remaining < 60) {
712             strftime(timeBuf, sizeof(timeBuf), "%Ss", ts);
713         } else if (remaining < 3600) {
714             strftime(timeBuf, sizeof(timeBuf), "%M:%S", ts);
715         } else {
716             strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", ts);
717         }
718     }
719
720     progressBar->setFormat(barText.arg(fileName) + "\n%p%   ETA " + timeBuf);
721     progressBar->setValue(progressBar->value() + value);
722 }
723
724
725 DeleteThread::DeleteThread(const QFileInfoList &files) : FileManipulatorThread(files) {
726     barText = tr("deleting %1");
727 }
728
729
730 void DeleteThread::run() {
731     mutex.lock();
732
733     setBarSize(calculateFileSize(files, true));
734
735     processFiles(files);
736
737     sleep(0.5);
738     emit finished(this);
739 }
740
741
742 void DeleteThread::perform(const QFileInfo &file) {
743     remove(file, true);
744 }
745
746
747 CopyThread::CopyThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
748     barText = tr("copying %1");
749 }
750
751
752 void CopyThread::run() {
753     mutex.lock();
754
755     setBarSize(calculateFileSize(files, false, true));
756
757     processFiles(files);
758
759     sleep(0.5);
760     emit finished(this);
761 }
762
763
764 void CopyThread::perform(const QFileInfo &file) {
765     copy(file);
766 }
767
768
769 MoveThread::MoveThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
770     barText = tr("moving %1");
771 }
772
773
774 void MoveThread::run() {
775     mutex.lock();
776
777     rename(files, dest);
778
779     sleep(0.5);
780     emit finished(this);
781 }
782
783
784 void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
785     setBarSize(barSize + files.size());
786
787     for (int i = 0; i < files.size(); ++i) {
788         QString path = files[i].absoluteFilePath();
789         QFSFileEngine engine(path);
790         QFileInfo newFile(dest.absolutePath() + "/" + files[i].fileName());
791
792         updateFile(path);
793
794         OVERWRITE_PROMPT(files[i], newFile)
795
796         // if we are owerwriting dir over a dir, we will get SKIP_DIR
797         // as a response from OVERWRITE_PROMT meaning we should skip it
798         // (KEEP would mean to keep the files inside)
799         if (files[i].isDir() && newFile.exists() && newFile.isDir()) {
800             if (response == FileOperator::SKIP_DIR) {
801                 if (abort) break;
802                 updateProgress(1);
803                 removeExcludeFiles.insert(path);
804                 continue;
805             }
806         } else {
807             if (response == FileOperator::KEEP) {
808                 if (abort) break;
809                 updateProgress(1);
810                 removeExcludeFiles.insert(path);
811                 continue;
812             }
813         }
814
815         QString newPath(newFile.absoluteFilePath());
816         QFSFileEngine newEngine(newPath);
817
818         while (!abort && !engine.rename(newPath)) {
819             // source and target are on different partitions
820             // this should happen on the first file, unless some are skipped by overwrite prompt
821             // we calculate the actual file sizes, because from now on copy & remove takes over
822             if (errno == EXDEV) {
823                 overwriteAll = response;
824                 // hack: we already checked the first file we are sending to processFiles(...)
825                 // so we don't want to ask about this one again
826                 if (overwriteAll == FileOperator::NONE) overwriteAll = FileOperator::DONT_ASK_ONCE;
827
828                 QFileInfoList remainingFiles = files.mid(i);
829
830                 setBarSize(barValue + calculateFileSize(remainingFiles, true, true));
831
832                 processFiles(remainingFiles);
833
834                 barText = tr("deleting %1");
835
836                 remove(remainingFiles, true);
837
838                 // just to quit the loops, we are done
839                 abort = true;
840             // the target is nonempty dir. lets call this recursively and rename the contents one by one
841             } else if (errno == ENOTEMPTY || errno == EEXIST) {
842                 FileOperator::Response tmpResp = overwriteAll;
843                 overwriteAll = response;
844
845                 rename(listDirFiles(path), QDir(newPath));
846                 if (abort) break;
847
848                 overwriteAll = tmpResp;
849
850                 remove(files[i]);
851
852                 break;
853             // source and target are nonmatching types(file and dir)
854             // remove the target and let it loop once again
855             } else if (errno == ENOTDIR || errno == EISDIR) {
856                 if (!remove(newPath)) break;
857             } else {
858                 SHOW_ERROR_PROMPT(tr("Error moving %1."), path)
859
860                 if (response == FileOperator::IGNORE) {
861                     break;
862                 }
863             }
864         }
865             
866         if (abort) break;
867         updateProgress(1);
868     }
869 }
870
871
872 void MoveThread::perform(const QFileInfo &file) {
873     copy(file);
874 }