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