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