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