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