1 // case - file manager for N900
2 // Copyright (C) 2010 Lukas Hrazky <lukkash@email.cz>
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.
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.
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/>.
18 #include "fileoperator.h"
22 #include <QMessageBox>
23 #include <QHBoxLayout>
31 #define SHOW_ERROR_PROMPT(promptString, fileName) \
32 response = FileOperator::NONE; \
33 if (ignoreAll[errno]) { \
34 response = FileOperator::IGNORE; \
37 char *realBuf = strerror_r(errno, buf, 255); \
38 emit showErrorPrompt(this, promptString + " " + realBuf + ".", fileName, errno); \
39 waitCond.wait(&mutex); \
43 #define ERROR_PROMPT(operation, promptString, fileName) \
45 response = FileOperator::NONE; \
46 while (!abort && operation) { \
47 SHOW_ERROR_PROMPT(promptString, fileName) \
48 if (response == FileOperator::IGNORE) { \
55 #define ERROR_PROMPT_XP(operation, promptString, fileName, onIgnore, quitCmd) \
57 ERROR_PROMPT(operation, promptString, fileName) \
58 if (abort || response == FileOperator::IGNORE) { \
59 if (!abort) onIgnore; \
65 #define OVERWRITE_PROMPT(file, newFile) \
67 response = FileOperator::NONE; \
69 if (newFile.exists()) { \
70 if (overwriteAll != FileOperator::NONE) { \
71 response = overwriteAll; \
73 bool dirOverDir = false; \
74 if (newFile.isDir() && file.isDir()) dirOverDir = true; \
75 emit showOverwritePrompt(this, newFile.absoluteFilePath(), dirOverDir); \
76 waitCond.wait(&mutex); \
82 FileOperator::FileOperator(QWidget *parent) : QWidget(parent) {
83 QHBoxLayout *layout = new QHBoxLayout;
84 layout->setContentsMargins(0, 0, 0, 0);
85 layout->setSpacing(0);
90 QString FileOperator::shortenPath(const QString &path) {
91 QString homePath = QFSFileEngine::homePath();
92 QString result = path;
93 if (path.indexOf(homePath, 0) == 0) {
94 result.replace(0, homePath.size(), "~");
101 void FileOperator::deleteFiles(const QFileInfoList &files) {
103 if (files.size() == 1) {
104 title = tr("Delete file");
105 desc = tr("Are you sure you want to delete %1?")
106 .arg(FileOperator::shortenPath(files[0].absoluteFilePath()));
108 title = tr("Delete files");
109 desc = tr("You are about to delete %1 files. Are you sure you want to continue?").arg(files.size());
112 int confirm = QMessageBox::warning(
120 if(confirm == QMessageBox::Yes) {
121 caterNewThread(new DeleteThread(files));
126 void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) {
128 if (files.size() == 1) {
129 title = tr("Copy file");
130 desc = tr("Are you sure you want to copy %1 to %2?")
131 .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
132 .arg(FileOperator::shortenPath(destination.absolutePath()));
134 title = tr("Copy files");
135 desc = tr("You are about to copy %1 files to %2. Are you sure you want to continue?")
136 .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
139 int confirm = QMessageBox::warning(
147 if(confirm == QMessageBox::Yes) {
148 caterNewThread(new CopyThread(files, destination));
153 void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) {
154 // for move we don't wanna move to the same dir
155 if (files[0].absolutePath() == destination.absolutePath()) return;
158 if (files.size() == 1) {
159 title = tr("Move file");
160 desc = tr("Are you sure you want to move %1 to %2?")
161 .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
162 .arg(FileOperator::shortenPath(destination.absolutePath()));
164 title = tr("Move files");
165 desc = tr("You are about to move %1 files to %2. Are you sure you want to continue?")
166 .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
169 int confirm = QMessageBox::warning(
177 if(confirm == QMessageBox::Yes) {
178 caterNewThread(new MoveThread(files, destination));
183 void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator,
184 const QString &message,
185 const QString &fileName,
189 msgBox.addButton(QMessageBox::Cancel);
190 QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
191 QAbstractButton *retryButton = msgBox.addButton(QMessageBox::Retry);
192 QAbstractButton *ignoreButton = msgBox.addButton(QMessageBox::Ignore);
193 QAbstractButton *ignoreAllButton = msgBox.addButton(tr("Ignore All"), QMessageBox::AcceptRole);
194 msgBox.setText(message.arg(FileOperator::shortenPath(fileName)));
198 if (msgBox.clickedButton() == abortButton) {
199 manipulator->setResponse(ABORT);
200 } else if (msgBox.clickedButton() == retryButton) {
201 manipulator->setResponse(RETRY);
202 } else if (msgBox.clickedButton() == ignoreButton) {
203 manipulator->setResponse(IGNORE);
204 } else if (msgBox.clickedButton() == ignoreAllButton) {
205 manipulator->setResponse(IGNORE, true, err);
210 void FileOperator::showOverwritePrompt(
211 FileManipulatorThread* manipulator,
212 const QString &fileName,
213 const bool dirOverDir)
216 msgBox.addButton(QMessageBox::Cancel);
217 QAbstractButton *yesButton = msgBox.addButton(QMessageBox::Yes);
218 QAbstractButton *yesToAllButton = msgBox.addButton(QMessageBox::YesToAll);
219 QAbstractButton *noButton = msgBox.addButton(QMessageBox::No);
220 QAbstractButton *noToAllButton = msgBox.addButton(QMessageBox::NoToAll);
221 QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
222 QAbstractButton *askButton = 0;
225 msgBox.setText(tr("Directory %1 already exists. Overwrite the files inside?")
226 .arg(FileOperator::shortenPath(fileName)));
227 askButton = msgBox.addButton(tr("Ask"), QMessageBox::AcceptRole);
229 msgBox.setText(tr("File %1 already exists. Overwrite?").arg(FileOperator::shortenPath(fileName)));
234 if (msgBox.clickedButton() == abortButton) {
235 manipulator->setResponse(ABORT);
236 } else if (msgBox.clickedButton() == yesButton) {
237 manipulator->setResponse(OVERWRITE);
238 } else if (msgBox.clickedButton() == yesToAllButton) {
239 manipulator->setResponse(OVERWRITE, true);
240 } else if (msgBox.clickedButton() == noButton) {
241 manipulator->setResponse(KEEP);
242 } else if (msgBox.clickedButton() == noToAllButton) {
243 manipulator->setResponse(KEEP, true);
244 } else if (msgBox.clickedButton() == askButton) {
245 manipulator->setResponse(NONE, true);
250 void FileOperator::remove(FileManipulatorThread* manipulator) {
252 layout()->removeWidget(manipulator->progressBar);
253 manipulatorList.removeAll(manipulator);
258 void FileOperator::setBarSize(FileManipulatorThread* manipulator, unsigned int size) {
259 if (!manipulator->progressBar->maximum()) {
260 manipulator->startTime = time(0);
262 manipulator->progressBar->setMinimum(0);
263 manipulator->progressBar->setMaximum(size);
267 void FileOperator::updateProgress(FileManipulatorThread* manipulator, int value) {
268 manipulator->setText(value);
272 void FileOperator::caterNewThread(FileManipulatorThread *thread) {
273 manipulatorList.append(thread);
275 connect(thread, SIGNAL(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)),
276 this, SLOT(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)));
277 connect(thread, SIGNAL(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)),
278 this, SLOT(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)));
279 connect(thread, SIGNAL(finished(FileManipulatorThread*)),
280 this, SLOT(remove(FileManipulatorThread*)));
281 connect(thread, SIGNAL(setBarSize(FileManipulatorThread*, unsigned int)),
282 this, SLOT(setBarSize(FileManipulatorThread*, unsigned int)));
283 connect(thread, SIGNAL(updateProgress(FileManipulatorThread*, int)),
284 this, SLOT(updateProgress(FileManipulatorThread*, int)));
286 thread->progressBar->setValue(0);
288 layout()->addWidget(thread->progressBar);
289 thread->start(QThread::LowestPriority);
293 FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir dest) :
294 progressBar(new QProgressBar()),
298 response(FileOperator::NONE),
299 overwriteAll(FileOperator::NONE),
307 memset(ignoreAll, false, sizeof(ignoreAll));
308 progressBar->setMaximum(0);
309 QFont barFont = progressBar->font();
310 barFont.setPointSize(12);
311 progressBar->setFont(barFont);
312 progressBar->setFormat(tr("Gathering information..."));
313 progressBar->setMinimumHeight(44);
314 progressBar->setStyle(new QPlastiqueStyle);
315 //progressBar->setStyle(new QMotifStyle);
319 FileManipulatorThread::~FileManipulatorThread() {
324 void FileManipulatorThread::setResponse(
325 const FileOperator::Response response,
326 const bool applyToAll,
331 this->response = response;
334 if (response == FileOperator::KEEP
335 || response == FileOperator::OVERWRITE
336 || response == FileOperator::NONE)
338 overwriteAll = response;
341 if (response == FileOperator::IGNORE) {
342 ignoreAll[err] = true;
346 if (response == FileOperator::ABORT) abort = true;
353 void FileManipulatorThread::processFiles(const QFileInfoList &files) {
354 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
361 bool FileManipulatorThread::remove(QString &fileName, const bool ignoreDirNotEmpty) {
362 return remove(QFileInfo(fileName), ignoreDirNotEmpty);
366 bool FileManipulatorThread::remove(const QFileInfoList &files, const bool ignoreDirNotEmpty) {
368 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
369 if (!remove(*it, ignoreDirNotEmpty)) res = false;
376 bool FileManipulatorThread::remove(const QFileInfo &file, const bool ignoreDirNotEmpty) {
377 QString path = file.absoluteFilePath();
378 QFSFileEngine engine(path);
381 QFileInfoList list = listDirFiles(path);
383 if (ignoreDirNotEmpty && list.size()) return true;
385 if (!remove(list, ignoreDirNotEmpty)) return false;
387 ERROR_PROMPT(!engine.rmdir(path, false),
388 tr("Error deleting directory %1."), path)
390 ERROR_PROMPT(!engine.remove(),
391 tr("Error deleting file %1."), path)
394 if (abort || response == FileOperator::IGNORE) return false;
399 void FileManipulatorThread::copy(const QFileInfo &file, const bool removeAfterCopy) {
400 std::cout << (removeAfterCopy ? "MOVING " : "COPYING ") << file.absoluteFilePath().toStdString()
401 << " to " << dest.absolutePath().toStdString() << std::endl;
403 QString path(file.absoluteFilePath());
404 QString newPath(dest.absolutePath() + "/" + file.fileName());
405 QFSFileEngine engine(path);
406 QFSFileEngine newEngine(newPath);
407 QFileInfo newFile(newPath);
411 // hack to prevent asking about the same file if we already asked in the rename(...) function
412 if (overwriteAll == FileOperator::DONT_ASK_ONCE) {
413 overwriteAll = FileOperator::NONE;
415 OVERWRITE_PROMPT(file, newFile)
420 // this loop is here only to allow easily breaking out to the end (and remove the source file/dir)
422 if (response == FileOperator::KEEP) {
423 updateProgress(fileSizeMap[path]);
427 FileOperator::Response overwriteResponse = response;
430 if (newFile.exists() && !newFile.isDir()) {
431 if(!remove(newPath)) {
432 updateProgress(fileSizeMap[path]);
435 newFile = QFileInfo(newPath);
438 if (!newFile.exists()) {
439 ERROR_PROMPT_XP(!engine.mkdir(newPath, false),
440 tr("Error creating directory %1."), newPath,
441 updateProgress(fileSizeMap[path]),
447 QDir destBackup = dest;
450 FileOperator::Response tmpResp = overwriteAll;
451 overwriteAll = overwriteResponse;
453 processFiles(listDirFiles(path));
455 overwriteAll = tmpResp;
457 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
458 tr("Error setting permissions for directory %1."), newPath)
464 ERROR_PROMPT_XP(engine.isSequential(),
465 tr("Cannot copy sequential file %1."), path,
466 updateProgress(fileSizeMap[path]),
469 if (newFile.exists() && newFile.isDir()) {
470 ERROR_PROMPT_XP(!remove(newPath),
471 tr("Cannot replace directory %1 due to previous errors."), newPath,
472 updateProgress(fileSizeMap[path]),
476 ERROR_PROMPT_XP(!engine.open(QIODevice::ReadOnly),
477 tr("Error reading file %1."), path,
478 updateProgress(fileSizeMap[path]),
482 while (!abort && !ignore) {
485 ERROR_PROMPT(!newEngine.open(QIODevice::WriteOnly | QIODevice::Truncate),
486 tr("Error writing file %1."), newPath)
488 if (abort || response == FileOperator::IGNORE) {
489 if (response == FileOperator::IGNORE) {
490 updateProgress(fileSizeMap[path] - fileValue);
499 while ((bytes = engine.read(block, sizeof(block))) > 0) {
500 if (bytes == -1 || bytes != newEngine.write(block, bytes)) {
502 SHOW_ERROR_PROMPT(tr("Error while reading from file %1."), path);
504 SHOW_ERROR_PROMPT(tr("Error while writing to file %1."), newPath);
508 if (response == FileOperator::IGNORE) {
509 updateProgress(fileSizeMap[path] - fileValue);
512 updateProgress(-fileValue);
528 if (abort || ignore) {
531 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
532 tr("Error setting permissions for file %1."), newPath)
539 if (removeAfterCopy && !abort) remove(path, true);
543 unsigned int FileManipulatorThread::countFiles(const QFileInfoList &files) {
544 unsigned int res = 0;
546 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
547 unsigned int size = 1;
550 size += countFiles(listDirFiles(it->absoluteFilePath()));
554 fileSizeMap[it->absoluteFilePath()] = size;
561 unsigned int FileManipulatorThread::calculateFileSize(const QFileInfoList &files) {
562 unsigned int res = 0;
564 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
565 unsigned int size = 1;
568 size += calculateFileSize(listDirFiles(it->absoluteFilePath()));
570 size = ceil(static_cast<float>(it->size()) / 524288);
574 fileSizeMap[it->absoluteFilePath()] = size;
581 QFileInfoList FileManipulatorThread::listDirFiles(const QString &dirPath) {
583 return dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden);
587 void FileManipulatorThread::setBarSize(unsigned int size) {
589 emit setBarSize(this, size);
593 void FileManipulatorThread::updateProgress(int value) {
596 emit updateProgress(this, value);
600 void FileManipulatorThread::updateFile(const QString &name) {
602 fileName = FileOperator::shortenPath(name);
603 emit updateProgress(this, 0);
607 void FileManipulatorThread::setText(int value) {
608 if (progressBar->value() + value > progressBar->maximum()) {
609 std::cout << "WARNING, EXCEEDING MAXIMUM BY " << value << std::endl;
612 time_t now = time(0);
613 if (lastTimeUpdate < now) {
614 lastTimeUpdate = now;
616 time_t elapsed = now - startTime;
617 time_t remaining = (time_t) ((float) elapsed / barValue * (barSize - barValue));
618 struct tm *ts = gmtime(&remaining);
620 if (remaining < 60) {
621 strftime(timeBuf, sizeof(timeBuf), "%Ss", ts);
622 } else if (remaining < 3600) {
623 strftime(timeBuf, sizeof(timeBuf), "%M:%S", ts);
625 strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", ts);
629 progressBar->setFormat(barText.arg(fileName) + "\n%p% ETA " + timeBuf);
630 progressBar->setValue(progressBar->value() + value);
634 DeleteThread::DeleteThread(const QFileInfoList &files) : FileManipulatorThread(files) {
635 barText = tr("deleting %1");
639 void DeleteThread::run() {
642 setBarSize(countFiles(files));
651 void DeleteThread::perform(const QFileInfo &file) {
652 std::cout << "DELETING " << file.absoluteFilePath().toStdString() << std::endl;
654 QString path = file.absoluteFilePath();
655 QFSFileEngine engine(path);
660 processFiles(listDirFiles(path));
662 if (!listDirFiles(path).size()) {
663 ERROR_PROMPT(!engine.rmdir(path, false),
664 tr("Error deleting directory %1."), path)
667 ERROR_PROMPT(!engine.remove(),
668 tr("Error deleting file %1."), path)
671 if (!abort) updateProgress(1);
675 CopyThread::CopyThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
676 barText = tr("copying %1");
680 void CopyThread::run() {
683 setBarSize(calculateFileSize(files));
692 void CopyThread::perform(const QFileInfo &file) {
697 MoveThread::MoveThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
698 barText = tr("moving %1");
702 void MoveThread::run() {
712 void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
713 setBarSize(barSize + files.size());
715 for (int i = 0; i < files.size(); ++i) {
716 QString path = files[i].absoluteFilePath();
717 QFSFileEngine engine(path);
718 QString newPath = dest.absolutePath() + "/" + files[i].fileName();
722 OVERWRITE_PROMPT(files[i], QFileInfo(newPath))
724 if (response == FileOperator::KEEP) {
731 while (!abort && !engine.rename(newPath)) {
732 // source and target are on different partitions
733 // this should happen on the first file, unless some are skipped by overwrite prompt
734 // we calculate the actual file sizes, because from now on copy & remove takes over
735 if (errno == EXDEV) {
736 setBarSize(barValue + calculateFileSize(files));
738 FileOperator::Response tmpResp = overwriteAll;
739 overwriteAll = response;
740 // hack: we already checked the first file we are sending to processFiles(...)
741 // so we don't want to ask about this one again
742 if (overwriteAll == FileOperator::NONE) overwriteAll = FileOperator::DONT_ASK_ONCE;
744 processFiles(files.mid(i));
746 overwriteAll = tmpResp;
748 // just to quit the loops, we are done
750 // the target is nonempty dir. lets call this recursively and rename the contents one by one
751 } else if (errno == ENOTEMPTY || errno == EEXIST) {
752 FileOperator::Response tmpResp = overwriteAll;
753 overwriteAll = response;
755 rename(listDirFiles(path), QDir(newPath));
758 overwriteAll = tmpResp;
760 ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
763 // source and target are nonmatching types(file and dir)
764 // remove the target and let it loop once again
765 } else if (errno == ENOTDIR || errno == EISDIR) {
766 if (!remove(newPath)) break;
768 SHOW_ERROR_PROMPT(tr("Error moving %1."), path)
770 if (response == FileOperator::IGNORE) {
782 void MoveThread::perform(const QFileInfo &file) {