altered behavior of the 'move' operation
[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 <math.h>
27 #include <errno.h>
28 #include <iostream>
29
30
31 #define BLOCK_SIZE 524288
32
33
34 #define SHOW_ERROR_PROMPT(promptString, fileName)                                           \
35     response = FileOperator::NONE;                                                          \
36     if (ignoreAll[errno]) {                                                                 \
37         response = FileOperator::IGNORE;                                                    \
38     } else {                                                                                \
39         char buf[255];                                                                      \
40         char *realBuf = strerror_r(errno, buf, 255);                                        \
41         emit showErrorPrompt(this, promptString + " " + realBuf + ".", fileName, errno);    \
42         waitCond.wait(&mutex);                                                              \
43     }
44
45
46 #define ERROR_PROMPT(operation, promptString, fileName)                                     \
47 {                                                                                           \
48     response = FileOperator::NONE;                                                          \
49     while (!abort && operation) {                                                           \
50         SHOW_ERROR_PROMPT(promptString, fileName)                                           \
51         if (response == FileOperator::IGNORE) {                                             \
52             break;                                                                          \
53         }                                                                                   \
54     }                                                                                       \
55 }
56
57
58 #define ERROR_PROMPT_XP(operation, promptString, fileName, onIgnore, quitCmd)               \
59 {                                                                                           \
60     ERROR_PROMPT(operation, promptString, fileName)                                         \
61     if (abort || response == FileOperator::IGNORE) {                                        \
62         if (!abort) onIgnore;                                                               \
63         quitCmd;                                                                            \
64     }                                                                                       \
65 }
66
67
68 #define OVERWRITE_PROMPT(file, newFile)                                                     \
69 {                                                                                           \
70     response = FileOperator::NONE;                                                          \
71                                                                                             \
72     if (newFile.exists()) {                                                                 \
73         if (overwriteAll != FileOperator::NONE) {                                           \
74             response = overwriteAll;                                                        \
75         } else {                                                                            \
76             bool dirOverDir = false;                                                        \
77             if (newFile.isDir() && file.isDir()) dirOverDir = true;                         \
78             emit showOverwritePrompt(this, newFile.absoluteFilePath(), dirOverDir);         \
79             waitCond.wait(&mutex);                                                          \
80         }                                                                                   \
81     }                                                                                       \
82 }
83
84
85 FileOperator::FileOperator(QWidget *parent) : QWidget(parent) {
86     QHBoxLayout *layout = new QHBoxLayout;
87     layout->setContentsMargins(0, 0, 0, 0);
88     layout->setSpacing(0);
89     setLayout(layout);
90 }
91
92
93 QString FileOperator::shortenPath(const QString &path) {
94     QString homePath = QFSFileEngine::homePath();
95     QString result = path;
96     if (path.indexOf(homePath, 0) == 0) {
97         result.replace(0, homePath.size(), "~");
98     }
99
100     return result;
101 }
102
103
104 void FileOperator::deleteFiles(const QFileInfoList &files) {
105     QString title, desc;
106     if (files.size() == 1) {
107         title = tr("Delete file");
108         desc = tr("Are you sure you want to delete %1?")
109             .arg(FileOperator::shortenPath(files[0].absoluteFilePath()));
110     } else {
111         title = tr("Delete files");
112         desc = tr("You are about to delete %1 files. Are you sure you want to continue?").arg(files.size());
113     }
114
115     int confirm = QMessageBox::warning(
116         0,
117         title,
118         desc,
119         QMessageBox::Yes,
120         QMessageBox::No
121     );
122
123     if(confirm == QMessageBox::Yes) {
124         caterNewThread(new DeleteThread(files));
125     }
126 }
127
128
129 void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) {
130     QString title, desc;
131     if (files.size() == 1) {
132         title = tr("Copy file");
133         desc = tr("Are you sure you want to copy %1 to %2?")
134             .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
135             .arg(FileOperator::shortenPath(destination.absolutePath()));
136     } else {
137         title = tr("Copy files");
138         desc = tr("You are about to copy %1 files to %2. Are you sure you want to continue?")
139             .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
140     }
141
142     int confirm = QMessageBox::warning(
143         0,
144         title,
145         desc,
146         QMessageBox::Yes,
147         QMessageBox::No
148     );
149
150     if(confirm == QMessageBox::Yes) {
151         caterNewThread(new CopyThread(files, destination));
152     }
153 }
154
155
156 void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) {
157     // for move we don't wanna move to the same dir
158     if (files[0].absolutePath() == destination.absolutePath()) return;
159
160     QString title, desc;
161     if (files.size() == 1) {
162         title = tr("Move file");
163         desc = tr("Are you sure you want to move %1 to %2?")
164             .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
165             .arg(FileOperator::shortenPath(destination.absolutePath()));
166     } else {
167         title = tr("Move files");
168         desc = tr("You are about to move %1 files to %2. Are you sure you want to continue?")
169             .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
170     }
171
172     int confirm = QMessageBox::warning(
173         0,
174         title,
175         desc,
176         QMessageBox::Yes,
177         QMessageBox::No
178     );
179
180     if(confirm == QMessageBox::Yes) {
181         caterNewThread(new MoveThread(files, destination));
182     }
183 }
184
185
186 void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator,
187     const QString &message,
188     const QString &fileName,
189     const int err)
190 {
191     QMessageBox msgBox;
192     msgBox.addButton(QMessageBox::Cancel);
193     QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
194     QAbstractButton *retryButton = msgBox.addButton(QMessageBox::Retry);
195     QAbstractButton *ignoreButton = msgBox.addButton(QMessageBox::Ignore);
196     QAbstractButton *ignoreAllButton = msgBox.addButton(tr("Ignore All"), QMessageBox::AcceptRole);
197     msgBox.setText(message.arg(FileOperator::shortenPath(fileName)));
198
199     msgBox.exec();
200
201     if (msgBox.clickedButton() == abortButton) {
202         manipulator->setResponse(ABORT);
203     } else if (msgBox.clickedButton() == retryButton) {
204         manipulator->setResponse(RETRY);
205     } else if (msgBox.clickedButton() == ignoreButton) {
206         manipulator->setResponse(IGNORE);
207     } else if (msgBox.clickedButton() == ignoreAllButton) {
208         manipulator->setResponse(IGNORE, true, err);
209     }
210 }
211
212
213 void FileOperator::showOverwritePrompt(
214     FileManipulatorThread* manipulator,
215     const QString &fileName,
216     const bool dirOverDir)
217 {
218     QMessageBox msgBox;
219     msgBox.addButton(QMessageBox::Cancel);
220     QAbstractButton *yesButton = msgBox.addButton(QMessageBox::Yes);
221     QAbstractButton *yesToAllButton = msgBox.addButton(QMessageBox::YesToAll);
222     QAbstractButton *noButton = msgBox.addButton(QMessageBox::No);
223     QAbstractButton *noToAllButton = msgBox.addButton(QMessageBox::NoToAll);
224     QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
225     QAbstractButton *askButton = 0;
226
227     if (dirOverDir) {
228         msgBox.setText(tr("Directory %1 already exists. Overwrite the files inside?")
229             .arg(FileOperator::shortenPath(fileName)));
230         askButton = msgBox.addButton(tr("Ask"), QMessageBox::AcceptRole);
231     } else {
232         msgBox.setText(tr("File %1 already exists. Overwrite?").arg(FileOperator::shortenPath(fileName)));
233     }
234
235     msgBox.exec();
236
237     if (msgBox.clickedButton() == abortButton) {
238         manipulator->setResponse(ABORT);
239     } else if (msgBox.clickedButton() == yesButton) {
240         manipulator->setResponse(OVERWRITE);
241     } else if (msgBox.clickedButton() == yesToAllButton) {
242         manipulator->setResponse(OVERWRITE, true);
243     } else if (msgBox.clickedButton() == noButton) {
244         manipulator->setResponse(KEEP);
245     } else if (msgBox.clickedButton() == noToAllButton) {
246         manipulator->setResponse(KEEP, true);
247     } else if (msgBox.clickedButton() == askButton) {
248         manipulator->setResponse(NONE, true);
249     }
250 }
251
252
253 void FileOperator::remove(FileManipulatorThread* manipulator) {
254     manipulator->wait();
255     layout()->removeWidget(manipulator->progressBar);
256     manipulatorList.removeAll(manipulator);
257     delete manipulator;
258 }
259
260
261 void FileOperator::setBarSize(FileManipulatorThread* manipulator, unsigned int size) {
262     if (!manipulator->progressBar->maximum()) {
263         manipulator->startTime = time(0);
264     }
265     manipulator->progressBar->setMinimum(0);
266     manipulator->progressBar->setMaximum(size);
267 }
268
269
270 void FileOperator::updateProgress(FileManipulatorThread* manipulator, int value) {
271     manipulator->setText(value);
272 }
273
274
275 void FileOperator::caterNewThread(FileManipulatorThread *thread) {
276     manipulatorList.append(thread);
277
278     connect(thread, SIGNAL(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)),
279         this, SLOT(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)));
280     connect(thread, SIGNAL(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)),
281         this, SLOT(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)));
282     connect(thread, SIGNAL(finished(FileManipulatorThread*)),
283         this, SLOT(remove(FileManipulatorThread*)));
284     connect(thread, SIGNAL(setBarSize(FileManipulatorThread*, unsigned int)),
285         this, SLOT(setBarSize(FileManipulatorThread*, unsigned int)));
286     connect(thread, SIGNAL(updateProgress(FileManipulatorThread*, int)),
287         this, SLOT(updateProgress(FileManipulatorThread*, int)));
288
289     thread->progressBar->setValue(0);
290
291     layout()->addWidget(thread->progressBar);
292     thread->start(QThread::LowestPriority);
293 }
294
295
296 FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir dest) :
297     progressBar(new QProgressBar()),
298     startTime(0),
299     files(files),
300     dest(dest),
301     response(FileOperator::NONE),
302     overwriteAll(FileOperator::NONE),
303     abort(false),
304     lastTimeUpdate(0),
305     barSize(0),
306     barValue(0),
307     fileSize(0),
308     fileValue(0)
309 {
310     memset(ignoreAll, false, sizeof(ignoreAll));
311     progressBar->setMaximum(0);
312     QFont barFont = progressBar->font();
313     barFont.setPointSize(12);
314     progressBar->setFont(barFont);
315     progressBar->setFormat(tr("Gathering information..."));
316     progressBar->setMinimumHeight(44);
317     progressBar->setStyle(new QPlastiqueStyle);
318     //progressBar->setStyle(new QMotifStyle);
319 }
320
321
322 FileManipulatorThread::~FileManipulatorThread() {
323     if (progressBar->value() < progressBar->maximum()) {
324         std::cout << "WARNING: deleting a progressbar which's value " << progressBar->value() <<
325             " has not reached maximum of " << progressBar->maximum() << std::endl;
326     }
327     delete progressBar;
328 }
329
330
331 void FileManipulatorThread::setResponse(
332     const FileOperator::Response response,
333     const bool applyToAll,
334     const int err)
335 {
336     mutex.lock();
337
338     this->response = response;
339
340     if (applyToAll) {
341         if (response == FileOperator::KEEP
342             || response == FileOperator::OVERWRITE
343             || response == FileOperator::NONE)
344         {
345             overwriteAll = response;
346         }
347
348         if (response == FileOperator::IGNORE) {
349             ignoreAll[err] = true;
350         }
351     }
352
353     if (response == FileOperator::ABORT) abort = true;
354
355     mutex.unlock();
356     waitCond.wakeAll();
357 }
358
359
360 void FileManipulatorThread::processFiles(const QFileInfoList &files) {
361     for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
362         perform(*it);
363         if (abort) break;
364     }
365 }
366
367
368 bool FileManipulatorThread::remove(QString &fileName, const bool doUpdates) {
369     return remove(QFileInfo(fileName), doUpdates);
370 }
371
372
373 bool FileManipulatorThread::remove(const QFileInfoList &files, const bool doUpdates) {
374     bool res = true;
375     for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
376         if (!remove(*it, doUpdates)) res = false;
377         if (abort) break;
378     }
379     return res;
380 }
381
382
383 bool FileManipulatorThread::remove(const QFileInfo &file, const bool doUpdates) {
384     std::cout << "DELETING " << file.absoluteFilePath().toStdString() << std::endl;
385
386     QString path = file.absoluteFilePath();
387     QFSFileEngine engine(path);
388
389     if (doUpdates) updateFile(path);
390
391     if (file.isDir()) {
392         if (!remove(listDirFiles(path), doUpdates)) return false;
393
394         if (!listDirFiles(path).size()) {
395             ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
396         }
397     } else {
398         ERROR_PROMPT(!engine.remove(), tr("Error deleting file %1."), path)
399     }
400
401     if (!abort && doUpdates) updateProgress(1);
402
403     if (abort || response == FileOperator::IGNORE) return false;
404     return true;
405 }
406
407
408 void FileManipulatorThread::copy(const QFileInfo &file) {
409     std::cout << "COPYING " << file.absoluteFilePath().toStdString()
410         << " to " << dest.absolutePath().toStdString() << std::endl;
411
412     QString path(file.absoluteFilePath());
413     QString newPath(dest.absolutePath() + "/" + file.fileName());
414     QFSFileEngine engine(path);
415     QFSFileEngine newEngine(newPath);
416     QFileInfo newFile(newPath);
417
418     updateFile(path);
419
420     // hack to prevent asking about the same file if we already asked in the rename(...) function
421     if (overwriteAll == FileOperator::DONT_ASK_ONCE) {
422         overwriteAll = FileOperator::NONE;
423     } else {
424         OVERWRITE_PROMPT(file, newFile)
425     }
426
427     if (abort) return;
428
429     if (response == FileOperator::KEEP) {
430         updateProgress(fileSizeMap[path]);
431         return;
432     }
433
434     if (file.isDir()) {
435         FileOperator::Response overwriteResponse = response;
436
437         if (newFile.exists() && !newFile.isDir()) {
438             if(!remove(newPath)) {
439                 updateProgress(fileSizeMap[path]);
440                 return;
441             }
442             newFile = QFileInfo(newPath);
443         }
444
445         if (!newFile.exists()) {
446             ERROR_PROMPT_XP(!engine.mkdir(newPath, false),
447                 tr("Error creating directory %1."), newPath,
448                 updateProgress(fileSizeMap[path]),
449                 return)
450         }
451
452         updateProgress(1);
453         
454         QDir destBackup = dest;
455         dest = newPath;
456
457         FileOperator::Response tmpResp = overwriteAll;
458         overwriteAll = overwriteResponse;
459
460         processFiles(listDirFiles(path));
461
462         overwriteAll = tmpResp;
463
464         ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
465             tr("Error setting permissions for directory %1."), newPath)
466
467         if (abort) return;
468
469         dest = destBackup;
470     } else {
471         ERROR_PROMPT_XP(engine.isSequential(),
472             tr("Cannot copy sequential file %1."), path,
473             updateProgress(fileSizeMap[path]),
474             return)
475
476         if (newFile.exists() && newFile.isDir()) {
477             ERROR_PROMPT_XP(!remove(newPath),
478                 tr("Cannot replace directory %1 due to previous errors."), newPath,
479                 updateProgress(fileSizeMap[path]),
480                 return)
481         }
482
483         ERROR_PROMPT_XP(!engine.open(QIODevice::ReadOnly),
484             tr("Error reading file %1."), path,
485             updateProgress(fileSizeMap[path]),
486             return)
487
488         bool ignore = false;
489         while (!abort && !ignore) {
490             engine.seek(0);
491
492             ERROR_PROMPT(!newEngine.open(QIODevice::WriteOnly | QIODevice::Truncate),
493                 tr("Error writing file %1."), newPath)
494
495             if (abort || response == FileOperator::IGNORE) {
496                 if (response == FileOperator::IGNORE) {
497                     updateProgress(fileSizeMap[path] - fileValue);
498                     ignore = true;
499                 }
500                 break;
501             }
502
503             bool error = false;
504             char block[BLOCK_SIZE];
505             qint64 bytes;
506             while ((bytes = engine.read(block, sizeof(block))) > 0) {
507                 if (bytes == -1 || bytes != newEngine.write(block, bytes)) {
508                     if (bytes == -1) {
509                         SHOW_ERROR_PROMPT(tr("Error while reading from file %1."), path);
510                     } else {
511                         SHOW_ERROR_PROMPT(tr("Error while writing to file %1."), newPath);
512                     }
513
514                     if (!abort) {
515                         if (response == FileOperator::IGNORE) {
516                             updateProgress(fileSizeMap[path] - fileValue);
517                             ignore = true;
518                         } else {
519                             updateProgress(-fileValue);
520                         }
521                     }
522                     error = true;
523                     break;
524                 }
525
526                 updateProgress(1);
527             }
528
529             if (!error) break;
530         }
531
532         engine.close();
533         newEngine.close();
534
535         if (abort || ignore) {
536             newEngine.remove();
537         } else {
538             ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
539                 tr("Error setting permissions for file %1."), newPath)
540         }
541     }
542 }
543
544
545 unsigned int FileManipulatorThread::calculateFileSize(const QFileInfoList &files,
546     const bool count,
547     const bool addSize)
548 {
549     unsigned int res = 0;
550
551     for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
552         unsigned int size = 0;
553
554         if (it->isDir()) {
555             size += calculateFileSize(listDirFiles(it->absoluteFilePath()), count, addSize);
556         }
557
558         if (addSize) {
559             if (it->isDir()) {
560                 ++size;
561             } else {
562                 size += ceil(static_cast<float>(it->size()) / BLOCK_SIZE);
563             }
564             fileSizeMap[it->absoluteFilePath()] = size;
565         }
566
567         if (count) {
568             ++size;
569         }
570
571         res += size;
572     }
573
574     return res;
575 }
576
577
578 QFileInfoList FileManipulatorThread::listDirFiles(const QString &dirPath) {
579     QDir dir = dirPath;
580     return dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden);
581 }
582
583
584 void FileManipulatorThread::setBarSize(unsigned int size) {
585     barSize = size;
586     emit setBarSize(this, size);
587 }
588
589
590 void FileManipulatorThread::updateProgress(int value) {
591     barValue += value;
592     fileValue += value;
593     emit updateProgress(this, value);
594 }
595
596
597 void FileManipulatorThread::updateFile(const QString &name) {
598     fileValue = 0;
599     fileName = FileOperator::shortenPath(name);
600     emit updateProgress(this, 0);
601 }
602
603
604 void FileManipulatorThread::setText(int value) {
605     if (progressBar->value() + value > progressBar->maximum()) {
606         std::cout << "WARNING: exceeding progressbar maximum (" << progressBar->maximum()
607             << ") by " << value << std::endl;
608     }
609  
610     time_t now = time(0);
611     if (lastTimeUpdate < now) {
612         lastTimeUpdate = now;
613
614         time_t elapsed = now - startTime;
615         time_t remaining = (time_t) ((float) elapsed / barValue * (barSize - barValue));
616         struct tm *ts = gmtime(&remaining);
617         
618         if (remaining < 60) {
619             strftime(timeBuf, sizeof(timeBuf), "%Ss", ts);
620         } else if (remaining < 3600) {
621             strftime(timeBuf, sizeof(timeBuf), "%M:%S", ts);
622         } else {
623             strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", ts);
624         }
625     }
626
627     progressBar->setFormat(barText.arg(fileName) + "\n%p%   ETA " + timeBuf);
628     progressBar->setValue(progressBar->value() + value);
629 }
630
631
632 DeleteThread::DeleteThread(const QFileInfoList &files) : FileManipulatorThread(files) {
633     barText = tr("deleting %1");
634 }
635
636
637 void DeleteThread::run() {
638     mutex.lock();
639
640     setBarSize(calculateFileSize(files, true));
641
642     processFiles(files);
643
644     sleep(0.5);
645     emit finished(this);
646 }
647
648
649 void DeleteThread::perform(const QFileInfo &file) {
650     remove(file, true);
651 }
652
653
654 CopyThread::CopyThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
655     barText = tr("copying %1");
656 }
657
658
659 void CopyThread::run() {
660     mutex.lock();
661
662     setBarSize(calculateFileSize(files, false, true));
663
664     processFiles(files);
665
666     sleep(0.5);
667     emit finished(this);
668 }
669
670
671 void CopyThread::perform(const QFileInfo &file) {
672     copy(file);
673 }
674
675
676 MoveThread::MoveThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
677     barText = tr("moving %1");
678 }
679
680
681 void MoveThread::run() {
682     mutex.lock();
683
684     rename(files, dest);
685
686     sleep(0.5);
687     emit finished(this);
688 }
689
690
691 void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
692     setBarSize(barSize + files.size());
693
694     for (int i = 0; i < files.size(); ++i) {
695         QString path = files[i].absoluteFilePath();
696         QFSFileEngine engine(path);
697         QString newPath = dest.absolutePath() + "/" + files[i].fileName();
698
699         updateFile(path);
700
701         OVERWRITE_PROMPT(files[i], QFileInfo(newPath))
702
703         if (response == FileOperator::KEEP) {
704             // TODO lets not remove the source for now, I'm not sure what is correct behavior
705             // remove(path);
706             if (abort) break;
707             updateProgress(1);
708             continue;
709         }
710
711         while (!abort && !engine.rename(newPath)) {
712             // source and target are on different partitions
713             // this should happen on the first file, unless some are skipped by overwrite prompt
714             // we calculate the actual file sizes, because from now on copy & remove takes over
715             if (errno == EXDEV) {
716                 overwriteAll = response;
717                 // hack: we already checked the first file we are sending to processFiles(...)
718                 // so we don't want to ask about this one again
719                 if (overwriteAll == FileOperator::NONE) overwriteAll = FileOperator::DONT_ASK_ONCE;
720
721                 QFileInfoList remainingFiles = files.mid(i);
722
723                 setBarSize(barValue + calculateFileSize(remainingFiles, true, true));
724
725                 processFiles(remainingFiles);
726
727                 barText = tr("deleting %1");
728
729                 remove(remainingFiles, true);
730
731                 // just to quit the loops, we are done
732                 abort = true;
733             // the target is nonempty dir. lets call this recursively and rename the contents one by one
734             } else if (errno == ENOTEMPTY || errno == EEXIST) {
735                 FileOperator::Response tmpResp = overwriteAll;
736                 overwriteAll = response;
737
738                 rename(listDirFiles(path), QDir(newPath));
739                 if (abort) break;
740
741                 overwriteAll = tmpResp;
742
743                 ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
744
745                 break;
746             // source and target are nonmatching types(file and dir)
747             // remove the target and let it loop once again
748             } else if (errno == ENOTDIR || errno == EISDIR) {
749                 if (!remove(newPath)) break;
750             } else {
751                 SHOW_ERROR_PROMPT(tr("Error moving %1."), path)
752
753                 if (response == FileOperator::IGNORE) {
754                     break;
755                 }
756             }
757         }
758             
759         if (abort) break;
760         updateProgress(1);
761     }
762 }
763
764
765 void MoveThread::perform(const QFileInfo &file) {
766     copy(file);
767 }