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