1 // case - file manager for N900
2 // Copyright (C) 2010 Lukas Hrazky
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) \
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 + ".", errno); \
39 waitCond.wait(&mutex); \
43 #define ERROR_PROMPT(operation, promptString) \
45 response = FileOperator::NONE; \
46 while (!abort && operation) { \
47 SHOW_ERROR_PROMPT(promptString) \
48 if (response == FileOperator::IGNORE) { \
55 #define ERROR_PROMPT_XP(operation, promptString, onIgnore, quitCmd) \
57 ERROR_PROMPT(operation, promptString) \
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 void FileOperator::deleteFiles(const QFileInfoList &files) {
92 if (files.size() == 1) {
93 title = tr("Delete file");
94 desc = tr("Are you sure you want to delete %1?").arg(files[0].absoluteFilePath());
96 title = tr("Delete files");
97 desc = tr("You are about to delete %1 files. Are you sure you want to continue?").arg(files.size());
100 int confirm = QMessageBox::warning(
108 if(confirm == QMessageBox::Yes) {
109 caterNewThread(new DeleteThread(files));
114 void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) {
116 if (files.size() == 1) {
117 title = tr("Copy file");
118 desc = tr("Are you sure you want to copy %1 to %2?").arg(files[0].absoluteFilePath())
119 .arg(destination.absolutePath());
121 title = tr("Copy files");
122 desc = tr("You are about to copy %1 files to %2. Are you sure you want to continue?")
123 .arg(files.size()).arg(destination.absolutePath());
126 int confirm = QMessageBox::warning(
134 if(confirm == QMessageBox::Yes) {
135 caterNewThread(new CopyThread(files, destination));
140 void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) {
141 // for move we don't wanna move to the same dir
142 if (files[0].absolutePath() == destination.absolutePath()) return;
145 if (files.size() == 1) {
146 title = tr("Move file");
147 desc = tr("Are you sure you want to move %1 to %2?").arg(files[0].absoluteFilePath())
148 .arg(destination.absolutePath());
150 title = tr("Move files");
151 desc = tr("You are about to move %1 files to %2. Are you sure you want to continue?")
152 .arg(files.size()).arg(destination.absolutePath());
155 int confirm = QMessageBox::warning(
163 if(confirm == QMessageBox::Yes) {
164 caterNewThread(new MoveThread(files, destination));
169 void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator, const QString &message, const int err) {
171 msgBox.addButton(QMessageBox::Cancel);
172 QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
173 QAbstractButton *retryButton = msgBox.addButton(QMessageBox::Retry);
174 QAbstractButton *ignoreButton = msgBox.addButton(QMessageBox::Ignore);
175 QAbstractButton *ignoreAllButton = msgBox.addButton(tr("Ignore All"), QMessageBox::AcceptRole);
176 msgBox.setText(message);
180 if (msgBox.clickedButton() == abortButton) {
181 manipulator->setResponse(ABORT);
182 } else if (msgBox.clickedButton() == retryButton) {
183 manipulator->setResponse(RETRY);
184 } else if (msgBox.clickedButton() == ignoreButton) {
185 manipulator->setResponse(IGNORE);
186 } else if (msgBox.clickedButton() == ignoreAllButton) {
187 manipulator->setResponse(IGNORE, true, err);
192 void FileOperator::showOverwritePrompt(
193 FileManipulatorThread* manipulator,
194 const QString &fileName,
195 const bool dirOverDir)
198 msgBox.addButton(QMessageBox::Cancel);
199 QAbstractButton *yesButton = msgBox.addButton(QMessageBox::Yes);
200 QAbstractButton *yesToAllButton = msgBox.addButton(QMessageBox::YesToAll);
201 QAbstractButton *noButton = msgBox.addButton(QMessageBox::No);
202 QAbstractButton *noToAllButton = msgBox.addButton(QMessageBox::NoToAll);
203 QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
204 QAbstractButton *askButton = 0;
207 msgBox.setText(tr("Directory %1 already exists. Overwrite the files inside?").arg(fileName));
208 askButton = msgBox.addButton(tr("Ask"), QMessageBox::AcceptRole);
210 msgBox.setText(tr("File %1 already exists. Overwrite?").arg(fileName));
215 if (msgBox.clickedButton() == abortButton) {
216 manipulator->setResponse(ABORT);
217 } else if (msgBox.clickedButton() == yesButton) {
218 manipulator->setResponse(OVERWRITE);
219 } else if (msgBox.clickedButton() == yesToAllButton) {
220 manipulator->setResponse(OVERWRITE, true);
221 } else if (msgBox.clickedButton() == noButton) {
222 manipulator->setResponse(KEEP);
223 } else if (msgBox.clickedButton() == noToAllButton) {
224 manipulator->setResponse(KEEP, true);
225 } else if (msgBox.clickedButton() == askButton) {
226 manipulator->setResponse(NONE, true);
231 void FileOperator::remove(FileManipulatorThread* manipulator) {
232 layout()->removeWidget(manipulator->widget);
233 manipulatorList.removeAll(manipulator);
238 void FileOperator::setBarSize(FileManipulatorThread* manipulator, unsigned int size) {
239 manipulator->widget->setMinimum(0);
240 manipulator->widget->setMaximum(size);
244 void FileOperator::updateProgress(FileManipulatorThread* manipulator, int value) {
245 if (manipulator->widget->value() + value > manipulator->widget->maximum()) {
246 std::cout << "WARNING, EXCEEDING MAXIMUM BY " << value << std::endl;
248 manipulator->widget->setValue(manipulator->widget->value() + value);
252 void FileOperator::caterNewThread(FileManipulatorThread *thread) {
253 manipulatorList.append(thread);
255 connect(thread, SIGNAL(showErrorPrompt(FileManipulatorThread*, const QString&, const int)),
256 this, SLOT(showErrorPrompt(FileManipulatorThread*, const QString&, const int)));
257 connect(thread, SIGNAL(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)),
258 this, SLOT(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)));
259 connect(thread, SIGNAL(finished(FileManipulatorThread*)),
260 this, SLOT(remove(FileManipulatorThread*)));
261 connect(thread, SIGNAL(setBarSize(FileManipulatorThread*, unsigned int)),
262 this, SLOT(setBarSize(FileManipulatorThread*, unsigned int)));
263 connect(thread, SIGNAL(updateProgress(FileManipulatorThread*, int)),
264 this, SLOT(updateProgress(FileManipulatorThread*, int)));
266 thread->widget->setValue(0);
268 layout()->addWidget(thread->widget);
273 FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir dest) :
274 widget(new QProgressBar()),
277 response(FileOperator::NONE),
278 overwriteAll(FileOperator::NONE),
285 memset(ignoreAll, false, sizeof(ignoreAll));
286 //widget->setStyle(new QPlastiqueStyle);
290 void FileManipulatorThread::setResponse(
291 const FileOperator::Response response,
292 const bool applyToAll,
297 this->response = response;
300 if (response == FileOperator::KEEP
301 || response == FileOperator::OVERWRITE
302 || response == FileOperator::NONE)
304 overwriteAll = response;
307 if (response == FileOperator::IGNORE) {
308 ignoreAll[err] = true;
312 if (response == FileOperator::ABORT) abort = true;
319 void FileManipulatorThread::processFiles(const QFileInfoList &files) {
320 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
327 bool FileManipulatorThread::remove(QString &fileName, const bool ignoreDirNotEmpty) {
328 return remove(QFileInfo(fileName), ignoreDirNotEmpty);
332 bool FileManipulatorThread::remove(const QFileInfoList &files, const bool ignoreDirNotEmpty) {
334 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
335 if (!remove(*it, ignoreDirNotEmpty)) res = false;
342 bool FileManipulatorThread::remove(const QFileInfo &file, const bool ignoreDirNotEmpty) {
343 QString path = file.absoluteFilePath();
344 QFSFileEngine engine(path);
347 QFileInfoList list = listDirFiles(path);
349 if (ignoreDirNotEmpty && list.size()) return true;
351 if (!remove(list, ignoreDirNotEmpty)) return false;
353 ERROR_PROMPT(!engine.rmdir(path, false),
354 tr("Error deleting directory %1.").arg(path))
356 ERROR_PROMPT(!engine.remove(),
357 tr("Error deleting file %1.").arg(path))
360 if (abort || response == FileOperator::IGNORE) return false;
365 void FileManipulatorThread::copy(const QFileInfo &file, const bool removeAfterCopy) {
366 std::cout << (removeAfterCopy ? "MOVING " : "COPYING ") << file.absoluteFilePath().toStdString()
367 << " to " << dest.absolutePath().toStdString() << std::endl;
369 QString path(file.absoluteFilePath());
370 QString newPath(dest.absolutePath() + "/" + file.fileName());
371 QFSFileEngine engine(path);
372 QFSFileEngine newEngine(newPath);
373 QFileInfo newFile(newPath);
377 // hack to prevent asking about the same file if we already asked in the rename(...) function
378 if (overwriteAll == FileOperator::DONT_ASK_ONCE) {
379 overwriteAll = FileOperator::NONE;
381 OVERWRITE_PROMPT(file, newFile)
386 // this loop is here only to allow easily breaking out to the end (and remove the source file/dir)
388 if (response == FileOperator::KEEP) {
389 updateProgress(fileSizeMap[path]);
393 FileOperator::Response overwriteResponse = response;
396 if (newFile.exists() && !newFile.isDir()) {
397 if(!remove(newPath)) {
398 updateProgress(fileSizeMap[path]);
401 newFile = QFileInfo(newPath);
404 if (!newFile.exists()) {
405 ERROR_PROMPT_XP(!engine.mkdir(newPath, false),
406 tr("Error creating directory %1.").arg(newPath),
407 updateProgress(fileSizeMap[path]),
413 QDir destBackup = dest;
416 FileOperator::Response tmpResp = overwriteAll;
417 overwriteAll = overwriteResponse;
419 processFiles(listDirFiles(path));
421 overwriteAll = tmpResp;
423 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
424 tr("Error setting permissions for directory %1.").arg(newPath))
430 ERROR_PROMPT_XP(engine.isSequential(),
431 tr("Cannot copy sequential file %1.").arg(path),
432 updateProgress(fileSizeMap[path]),
435 if (newFile.exists() && newFile.isDir()) {
436 ERROR_PROMPT_XP(!remove(newPath),
437 tr("Cannot replace directory %1 due to previous errors.").arg(newPath),
438 updateProgress(fileSizeMap[path]),
442 ERROR_PROMPT_XP(!engine.open(QIODevice::ReadOnly),
443 tr("Error reading file %1.").arg(path),
444 updateProgress(fileSizeMap[path]),
448 while (!abort && !ignore) {
451 ERROR_PROMPT(!newEngine.open(QIODevice::WriteOnly | QIODevice::Truncate),
452 tr("Error writing file %1.").arg(newPath))
454 if (abort || response == FileOperator::IGNORE) {
455 if (response == FileOperator::IGNORE) {
456 updateProgress(fileSizeMap[path] - fileValue);
465 while ((bytes = engine.read(block, sizeof(block))) > 0) {
466 if (bytes == -1 || bytes != newEngine.write(block, bytes)) {
468 SHOW_ERROR_PROMPT(tr("Error while reading from file %1.").arg(path));
470 SHOW_ERROR_PROMPT(tr("Error while writing to file %1.").arg(newPath));
474 if (response == FileOperator::IGNORE) {
475 updateProgress(fileSizeMap[path] - fileValue);
478 updateProgress(-fileValue);
494 if (abort || ignore) {
497 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
498 tr("Error setting permissions for file %1.").arg(newPath))
505 if (removeAfterCopy && !abort) remove(path, true);
509 unsigned int FileManipulatorThread::countFiles(const QFileInfoList &files) {
510 unsigned int res = 0;
512 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
513 unsigned int size = 1;
516 size += countFiles(listDirFiles(it->absoluteFilePath()));
520 fileSizeMap[it->absoluteFilePath()] = size;
527 unsigned int FileManipulatorThread::calculateFileSize(const QFileInfoList &files) {
528 unsigned int res = 0;
530 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
531 unsigned int size = 1;
534 size += calculateFileSize(listDirFiles(it->absoluteFilePath()));
536 size = ceil(static_cast<float>(it->size()) / 4096);
540 fileSizeMap[it->absoluteFilePath()] = size;
547 QFileInfoList FileManipulatorThread::listDirFiles(const QString &dirPath) {
549 return dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden);
553 void FileManipulatorThread::setBarSize(unsigned int size) {
555 emit setBarSize(this, size);
559 void FileManipulatorThread::updateProgress(int value) {
562 emit updateProgress(this, value);
566 void FileManipulatorThread::updateFile(const QString &fileName) {
568 emit updateFile(this, fileName);
572 void DeleteThread::run() {
575 setBarSize(countFiles(files));
584 void DeleteThread::perform(const QFileInfo &file) {
585 std::cout << "DELETING " << file.absoluteFilePath().toStdString() << std::endl;
587 QString path = file.absoluteFilePath();
588 QFSFileEngine engine(path);
591 processFiles(listDirFiles(path));
593 if (!listDirFiles(path).size()) {
594 ERROR_PROMPT(!engine.rmdir(path, false),
595 tr("Error deleting directory %1.").arg(path))
598 ERROR_PROMPT(!engine.remove(),
599 tr("Error deleting file %1.").arg(path))
602 if (!abort) updateProgress(1);
606 void CopyThread::run() {
609 setBarSize(calculateFileSize(files));
618 void CopyThread::perform(const QFileInfo &file) {
623 void MoveThread::run() {
633 void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
634 setBarSize(barSize + files.size());
636 for (int i = 0; i < files.size(); ++i) {
637 QString path = files[i].absoluteFilePath();
638 QFSFileEngine engine(path);
639 QString newPath = dest.absolutePath() + "/" + files[i].fileName();
643 OVERWRITE_PROMPT(files[i], QFileInfo(newPath))
645 if (response == FileOperator::KEEP) {
652 while (!abort && !engine.rename(newPath)) {
653 // source and target are on different partitions
654 // this should happen on the first file, unless some are skipped by overwrite prompt
655 // we calculate the actual file sizes, because from now on copy & remove takes over
656 if (errno == EXDEV) {
657 setBarSize(barValue + calculateFileSize(files));
659 FileOperator::Response tmpResp = overwriteAll;
660 overwriteAll = response;
661 // hack: we already checked the first file we are sending to processFiles(...)
662 // so we don't want to ask about this one again
663 if (overwriteAll == FileOperator::NONE) overwriteAll = FileOperator::DONT_ASK_ONCE;
665 processFiles(files.mid(i));
667 overwriteAll = tmpResp;
669 // just to quit the loops, we are done
671 // the target is nonempty dir. lets call this recursively and rename the contents one by one
672 } else if (errno == ENOTEMPTY || errno == EEXIST) {
673 FileOperator::Response tmpResp = overwriteAll;
674 overwriteAll = response;
676 rename(listDirFiles(path), QDir(newPath));
679 overwriteAll = tmpResp;
681 ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1.").arg(path))
684 // source and target are nonmatching types(file and dir)
685 // remove the target and let it loop once again
686 } else if (errno == ENOTDIR || errno == EISDIR) {
687 if (!remove(newPath)) break;
689 SHOW_ERROR_PROMPT(tr("Error moving %1.").arg(path))
691 if (response == FileOperator::IGNORE) {
703 void MoveThread::perform(const QFileInfo &file) {