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>
33 #define BLOCK_SIZE 524288
36 #define SHOW_ERROR_PROMPT(promptString, fileName) \
37 response = FileOperator::NONE; \
38 if (ignoreAll[errno]) { \
39 response = FileOperator::IGNORE; \
42 char *realBuf = strerror_r(errno, buf, 255); \
43 emit showErrorPrompt(this, promptString + " " + realBuf + ".", fileName, errno); \
44 waitCond.wait(&mutex); \
48 #define ERROR_PROMPT(operation, promptString, fileName) \
50 response = FileOperator::NONE; \
51 while (!abort && operation) { \
52 SHOW_ERROR_PROMPT(promptString, fileName) \
53 if (response == FileOperator::IGNORE) { \
60 #define SPECIAL_COPY_ERROR_PROMPT(operation, promptString, fileName) \
62 ERROR_PROMPT(operation, promptString, fileName) \
63 if (abort || response == FileOperator::IGNORE) { \
65 updateProgress(fileSizeMap[path]); \
66 removeExcludeFiles.insert(path); \
73 #define OVERWRITE_PROMPT(file, newFile) \
75 response = FileOperator::NONE; \
77 if (newFile.exists()) { \
78 if (overwriteAll != FileOperator::NONE) { \
79 response = overwriteAll; \
81 bool dirOverDir = false; \
82 if (newFile.isDir() && file.isDir()) dirOverDir = true; \
83 emit showOverwritePrompt(this, newFile.absoluteFilePath(), dirOverDir); \
84 waitCond.wait(&mutex); \
90 FileOperator::FileOperator(QWidget *parent) : QWidget(parent) {
91 QHBoxLayout *layout = new QHBoxLayout;
92 layout->setContentsMargins(0, 0, 0, 0);
93 layout->setSpacing(0);
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(), "~");
109 void FileOperator::deleteFiles(const QFileInfoList &files) {
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()));
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());
120 int confirm = QMessageBox::warning(
128 if(confirm == QMessageBox::Yes) {
129 caterNewThread(new DeleteThread(files));
134 void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) {
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()));
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()));
147 int confirm = QMessageBox::warning(
155 if(confirm == QMessageBox::Yes) {
156 caterNewThread(new CopyThread(files, destination));
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;
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()));
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()));
177 int confirm = QMessageBox::warning(
185 if(confirm == QMessageBox::Yes) {
186 caterNewThread(new MoveThread(files, destination));
191 void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator,
192 const QString &message,
193 const QString &fileName,
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)));
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);
218 void FileOperator::showOverwritePrompt(
219 FileManipulatorThread* manipulator,
220 const QString &fileName,
221 const bool dirOverDir)
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;
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);
239 msgBox.setText(tr("File %1 already exists. Overwrite?").arg(FileOperator::shortenPath(fileName)));
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);
262 void FileOperator::remove(FileManipulatorThread* manipulator) {
264 layout()->removeWidget(manipulator->progressBar);
265 manipulatorList.removeAll(manipulator);
270 void FileOperator::setBarSize(FileManipulatorThread* manipulator, unsigned int size) {
271 if (!manipulator->progressBar->maximum()) {
272 manipulator->startTime = time(0);
274 manipulator->progressBar->setMinimum(0);
275 manipulator->progressBar->setMaximum(size);
279 void FileOperator::updateProgress(FileManipulatorThread* manipulator, int value) {
280 manipulator->setText(value);
284 void FileOperator::caterNewThread(FileManipulatorThread *thread) {
285 manipulatorList.append(thread);
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)));
298 thread->progressBar->setValue(0);
300 layout()->addWidget(thread->progressBar);
301 thread->start(QThread::LowestPriority);
305 FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir dest) :
306 progressBar(new QProgressBar()),
310 response(FileOperator::NONE),
311 overwriteAll(FileOperator::NONE),
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);
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;
340 void FileManipulatorThread::setResponse(
341 const FileOperator::Response response,
342 const bool applyToAll,
347 this->response = response;
350 if (response == FileOperator::KEEP
351 || response == FileOperator::OVERWRITE
352 || response == FileOperator::NONE)
354 overwriteAll = response;
357 if (response == FileOperator::IGNORE) {
358 ignoreAll[err] = true;
362 if (response == FileOperator::ABORT) abort = true;
369 void FileManipulatorThread::processFiles(const QFileInfoList &files) {
370 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
377 bool FileManipulatorThread::remove(QString &fileName, const bool doUpdates) {
378 return remove(QFileInfo(fileName), doUpdates);
382 bool FileManipulatorThread::remove(const QFileInfoList &files, const bool doUpdates) {
384 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
385 if (!remove(*it, doUpdates)) res = false;
392 bool FileManipulatorThread::remove(const QFileInfo &file, const bool doUpdates) {
393 std::cout << "DELETING " << file.absoluteFilePath().toStdString() << std::endl;
395 QString path = file.absoluteFilePath();
397 if (removeExcludeFiles.contains(path)) {
398 if (doUpdates) updateProgress(1);
402 QFSFileEngine engine(path);
404 if (doUpdates) updateFile(path);
407 if (!remove(listDirFiles(path), doUpdates)) {
408 if (doUpdates) updateProgress(1);
412 if (!listDirFiles(path).size()) {
413 ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
416 ERROR_PROMPT(!engine.remove(), tr("Error deleting file %1."), path)
419 if (!abort && doUpdates) updateProgress(1);
421 if (abort || response == FileOperator::IGNORE) return false;
426 void FileManipulatorThread::copy(const QFileInfo &file) {
427 std::cout << "COPYING " << file.absoluteFilePath().toStdString()
428 << " to " << dest.absolutePath().toStdString() << std::endl;
430 QString path(file.absoluteFilePath());
431 QString newPath(dest.absolutePath() + "/" + file.fileName());
432 QFSFileEngine engine(path);
433 QFSFileEngine newEngine(newPath);
434 QFileInfo newFile(newPath);
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;
442 OVERWRITE_PROMPT(file, newFile)
448 // save the overwrite response, because the response variable will get ovewritten in remove(...)
449 FileOperator::Response overwriteResponse = response;
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);
459 // if it should not be kept, remove it and return on failure
460 if(!remove(newPath)) {
461 updateProgress(fileSizeMap[path]);
464 // create new info since we deleted the file - is it needed?
465 newFile = QFileInfo(newPath);
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);
476 if (!newFile.exists()) {
477 SPECIAL_COPY_ERROR_PROMPT(!engine.mkdir(newPath, false),
478 tr("Error creating directory %1."), newPath)
481 // we've done the job with the dir, so update progress and recurse into the dir
484 // change the dest for the recursion
485 QDir destBackup = dest;
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;
493 processFiles(listDirFiles(path));
495 overwriteAll = tmpResp;
497 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
498 tr("Error setting permissions for directory %1."), newPath)
504 if (response == FileOperator::KEEP) {
505 updateProgress(fileSizeMap[path]);
506 removeExcludeFiles.insert(path);
510 SPECIAL_COPY_ERROR_PROMPT(engine.isSequential(), tr("Cannot copy sequential file %1."), path)
512 if (newFile.exists() && newFile.isDir()) {
513 SPECIAL_COPY_ERROR_PROMPT(!remove(newPath),
514 tr("Cannot replace directory %1 due to previous errors."), newPath)
517 SPECIAL_COPY_ERROR_PROMPT(!engine.open(QIODevice::ReadOnly), tr("Error reading file %1."), path)
520 while (!abort && !ignore) {
524 ERROR_PROMPT(!newEngine.open(QIODevice::WriteOnly | QIODevice::Truncate),
525 tr("Error writing file %1."), newPath)
527 if (abort || response == FileOperator::IGNORE) {
528 if (response == FileOperator::IGNORE) {
529 updateProgress(fileSizeMap[path]);
530 removeExcludeFiles.insert(path);
537 char block[BLOCK_SIZE];
539 while ((bytes = engine.read(block, sizeof(block))) > 0) {
540 if (bytes == -1 || bytes != newEngine.write(block, bytes)) {
542 SHOW_ERROR_PROMPT(tr("Error while reading from file %1."), path);
544 SHOW_ERROR_PROMPT(tr("Error while writing to file %1."), newPath);
548 if (response == FileOperator::IGNORE) {
549 updateProgress(fileSizeMap[path] - fileValue);
550 removeExcludeFiles.insert(path);
553 updateProgress(-fileValue);
569 if (abort || ignore) {
572 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
573 tr("Error setting permissions for file %1."), newPath)
579 unsigned int FileManipulatorThread::calculateFileSize(const QFileInfoList &files,
583 unsigned int res = 0;
585 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
586 unsigned int size = 0;
589 size += calculateFileSize(listDirFiles(it->absoluteFilePath()), count, addSize);
596 size += ceil(static_cast<float>(it->size()) / BLOCK_SIZE);
598 fileSizeMap[it->absoluteFilePath()] = size;
612 QFileInfoList FileManipulatorThread::listDirFiles(const QString &dirPath) {
614 return dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden);
618 void FileManipulatorThread::setBarSize(unsigned int size) {
620 emit setBarSize(this, size);
624 void FileManipulatorThread::updateProgress(int value) {
627 emit updateProgress(this, value);
631 void FileManipulatorThread::updateFile(const QString &name) {
633 fileName = FileOperator::shortenPath(name);
634 emit updateProgress(this, 0);
638 void FileManipulatorThread::setText(int value) {
639 if (progressBar->value() + value > progressBar->maximum()) {
640 std::cout << "WARNING: exceeding progressbar maximum (" << progressBar->maximum()
641 << ") by " << value << std::endl;
644 time_t now = time(0);
645 if (lastTimeUpdate < now) {
646 lastTimeUpdate = now;
648 time_t elapsed = now - startTime;
649 time_t remaining = (time_t) ((float) elapsed / barValue * (barSize - barValue));
650 struct tm *ts = gmtime(&remaining);
652 if (remaining < 60) {
653 strftime(timeBuf, sizeof(timeBuf), "%Ss", ts);
654 } else if (remaining < 3600) {
655 strftime(timeBuf, sizeof(timeBuf), "%M:%S", ts);
657 strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", ts);
661 progressBar->setFormat(barText.arg(fileName) + "\n%p% ETA " + timeBuf);
662 progressBar->setValue(progressBar->value() + value);
666 DeleteThread::DeleteThread(const QFileInfoList &files) : FileManipulatorThread(files) {
667 barText = tr("deleting %1");
671 void DeleteThread::run() {
674 setBarSize(calculateFileSize(files, true));
683 void DeleteThread::perform(const QFileInfo &file) {
688 CopyThread::CopyThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
689 barText = tr("copying %1");
693 void CopyThread::run() {
696 setBarSize(calculateFileSize(files, false, true));
705 void CopyThread::perform(const QFileInfo &file) {
710 MoveThread::MoveThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
711 barText = tr("moving %1");
715 void MoveThread::run() {
725 void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
726 setBarSize(barSize + files.size());
728 for (int i = 0; i < files.size(); ++i) {
729 QString path = files[i].absoluteFilePath();
730 QFSFileEngine engine(path);
731 QString newPath = dest.absolutePath() + "/" + files[i].fileName();
732 QFileInfo newFile(newPath);
736 OVERWRITE_PROMPT(files[i], newFile)
738 if (files[i].isDir() && newFile.exists() && newFile.isDir()) {
739 if (response == FileOperator::SKIP_DIR) {
742 removeExcludeFiles.insert(path);
746 if (response == FileOperator::KEEP) {
749 removeExcludeFiles.insert(path);
754 while (!abort && !engine.rename(newPath)) {
755 // source and target are on different partitions
756 // this should happen on the first file, unless some are skipped by overwrite prompt
757 // we calculate the actual file sizes, because from now on copy & remove takes over
758 if (errno == EXDEV) {
759 overwriteAll = response;
760 // hack: we already checked the first file we are sending to processFiles(...)
761 // so we don't want to ask about this one again
762 if (overwriteAll == FileOperator::NONE) overwriteAll = FileOperator::DONT_ASK_ONCE;
764 QFileInfoList remainingFiles = files.mid(i);
766 setBarSize(barValue + calculateFileSize(remainingFiles, true, true));
768 processFiles(remainingFiles);
770 barText = tr("deleting %1");
772 remove(remainingFiles, true);
774 // just to quit the loops, we are done
776 // the target is nonempty dir. lets call this recursively and rename the contents one by one
777 } else if (errno == ENOTEMPTY || errno == EEXIST) {
778 FileOperator::Response tmpResp = overwriteAll;
779 overwriteAll = response;
781 rename(listDirFiles(path), QDir(newPath));
784 overwriteAll = tmpResp;
789 // source and target are nonmatching types(file and dir)
790 // remove the target and let it loop once again
791 } else if (errno == ENOTDIR || errno == EISDIR) {
792 if (!remove(newPath)) break;
794 SHOW_ERROR_PROMPT(tr("Error moving %1."), path)
796 if (response == FileOperator::IGNORE) {
808 void MoveThread::perform(const QFileInfo &file) {