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