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 BLOCK_SIZE 524288
34 #define SHOW_ERROR_PROMPT(promptString, fileName) \
35 response = FileOperator::NONE; \
36 if (ignoreAll[errno]) { \
37 response = FileOperator::IGNORE; \
40 char *realBuf = strerror_r(errno, buf, 255); \
41 emit showErrorPrompt(this, promptString + " " + realBuf + ".", fileName, errno); \
42 waitCond.wait(&mutex); \
46 #define ERROR_PROMPT(operation, promptString, fileName) \
48 response = FileOperator::NONE; \
49 while (!abort && operation) { \
50 SHOW_ERROR_PROMPT(promptString, fileName) \
51 if (response == FileOperator::IGNORE) { \
58 #define ERROR_PROMPT_XP(operation, promptString, fileName, onIgnore, quitCmd) \
60 ERROR_PROMPT(operation, promptString, fileName) \
61 if (abort || response == FileOperator::IGNORE) { \
62 if (!abort) onIgnore; \
68 #define OVERWRITE_PROMPT(file, newFile) \
70 response = FileOperator::NONE; \
72 if (newFile.exists()) { \
73 if (overwriteAll != FileOperator::NONE) { \
74 response = overwriteAll; \
76 bool dirOverDir = false; \
77 if (newFile.isDir() && file.isDir()) dirOverDir = true; \
78 emit showOverwritePrompt(this, newFile.absoluteFilePath(), dirOverDir); \
79 waitCond.wait(&mutex); \
85 FileOperator::FileOperator(QWidget *parent) : QWidget(parent) {
86 QHBoxLayout *layout = new QHBoxLayout;
87 layout->setContentsMargins(0, 0, 0, 0);
88 layout->setSpacing(0);
93 QString FileOperator::shortenPath(const QString &path) {
94 QString homePath = QFSFileEngine::homePath();
95 QString result = path;
96 if (path.indexOf(homePath, 0) == 0) {
97 result.replace(0, homePath.size(), "~");
104 void FileOperator::deleteFiles(const QFileInfoList &files) {
106 if (files.size() == 1) {
107 title = tr("Delete file");
108 desc = tr("Are you sure you want to delete %1?")
109 .arg(FileOperator::shortenPath(files[0].absoluteFilePath()));
111 title = tr("Delete files");
112 desc = tr("You are about to delete %1 files. Are you sure you want to continue?").arg(files.size());
115 int confirm = QMessageBox::warning(
123 if(confirm == QMessageBox::Yes) {
124 caterNewThread(new DeleteThread(files));
129 void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) {
131 if (files.size() == 1) {
132 title = tr("Copy file");
133 desc = tr("Are you sure you want to copy %1 to %2?")
134 .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
135 .arg(FileOperator::shortenPath(destination.absolutePath()));
137 title = tr("Copy files");
138 desc = tr("You are about to copy %1 files to %2. Are you sure you want to continue?")
139 .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
142 int confirm = QMessageBox::warning(
150 if(confirm == QMessageBox::Yes) {
151 caterNewThread(new CopyThread(files, destination));
156 void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) {
157 // for move we don't wanna move to the same dir
158 if (files[0].absolutePath() == destination.absolutePath()) return;
161 if (files.size() == 1) {
162 title = tr("Move file");
163 desc = tr("Are you sure you want to move %1 to %2?")
164 .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
165 .arg(FileOperator::shortenPath(destination.absolutePath()));
167 title = tr("Move files");
168 desc = tr("You are about to move %1 files to %2. Are you sure you want to continue?")
169 .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
172 int confirm = QMessageBox::warning(
180 if(confirm == QMessageBox::Yes) {
181 caterNewThread(new MoveThread(files, destination));
186 void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator,
187 const QString &message,
188 const QString &fileName,
192 msgBox.addButton(QMessageBox::Cancel);
193 QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
194 QAbstractButton *retryButton = msgBox.addButton(QMessageBox::Retry);
195 QAbstractButton *ignoreButton = msgBox.addButton(QMessageBox::Ignore);
196 QAbstractButton *ignoreAllButton = msgBox.addButton(tr("Ignore All"), QMessageBox::AcceptRole);
197 msgBox.setText(message.arg(FileOperator::shortenPath(fileName)));
201 if (msgBox.clickedButton() == abortButton) {
202 manipulator->setResponse(ABORT);
203 } else if (msgBox.clickedButton() == retryButton) {
204 manipulator->setResponse(RETRY);
205 } else if (msgBox.clickedButton() == ignoreButton) {
206 manipulator->setResponse(IGNORE);
207 } else if (msgBox.clickedButton() == ignoreAllButton) {
208 manipulator->setResponse(IGNORE, true, err);
213 void FileOperator::showOverwritePrompt(
214 FileManipulatorThread* manipulator,
215 const QString &fileName,
216 const bool dirOverDir)
219 msgBox.addButton(QMessageBox::Cancel);
220 QAbstractButton *yesButton = msgBox.addButton(QMessageBox::Yes);
221 QAbstractButton *yesToAllButton = msgBox.addButton(QMessageBox::YesToAll);
222 QAbstractButton *noButton = msgBox.addButton(QMessageBox::No);
223 QAbstractButton *noToAllButton = msgBox.addButton(QMessageBox::NoToAll);
224 QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
225 QAbstractButton *askButton = 0;
228 msgBox.setText(tr("Directory %1 already exists. Overwrite the files inside?")
229 .arg(FileOperator::shortenPath(fileName)));
230 askButton = msgBox.addButton(tr("Ask"), QMessageBox::AcceptRole);
232 msgBox.setText(tr("File %1 already exists. Overwrite?").arg(FileOperator::shortenPath(fileName)));
237 if (msgBox.clickedButton() == abortButton) {
238 manipulator->setResponse(ABORT);
239 } else if (msgBox.clickedButton() == yesButton) {
240 manipulator->setResponse(OVERWRITE);
241 } else if (msgBox.clickedButton() == yesToAllButton) {
242 manipulator->setResponse(OVERWRITE, true);
243 } else if (msgBox.clickedButton() == noButton) {
244 manipulator->setResponse(KEEP);
245 } else if (msgBox.clickedButton() == noToAllButton) {
246 manipulator->setResponse(KEEP, true);
247 } else if (msgBox.clickedButton() == askButton) {
248 manipulator->setResponse(NONE, true);
253 void FileOperator::remove(FileManipulatorThread* manipulator) {
255 layout()->removeWidget(manipulator->progressBar);
256 manipulatorList.removeAll(manipulator);
261 void FileOperator::setBarSize(FileManipulatorThread* manipulator, unsigned int size) {
262 if (!manipulator->progressBar->maximum()) {
263 manipulator->startTime = time(0);
265 manipulator->progressBar->setMinimum(0);
266 manipulator->progressBar->setMaximum(size);
270 void FileOperator::updateProgress(FileManipulatorThread* manipulator, int value) {
271 manipulator->setText(value);
275 void FileOperator::caterNewThread(FileManipulatorThread *thread) {
276 manipulatorList.append(thread);
278 connect(thread, SIGNAL(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)),
279 this, SLOT(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)));
280 connect(thread, SIGNAL(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)),
281 this, SLOT(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)));
282 connect(thread, SIGNAL(finished(FileManipulatorThread*)),
283 this, SLOT(remove(FileManipulatorThread*)));
284 connect(thread, SIGNAL(setBarSize(FileManipulatorThread*, unsigned int)),
285 this, SLOT(setBarSize(FileManipulatorThread*, unsigned int)));
286 connect(thread, SIGNAL(updateProgress(FileManipulatorThread*, int)),
287 this, SLOT(updateProgress(FileManipulatorThread*, int)));
289 thread->progressBar->setValue(0);
291 layout()->addWidget(thread->progressBar);
292 thread->start(QThread::LowestPriority);
296 FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir dest) :
297 progressBar(new QProgressBar()),
301 response(FileOperator::NONE),
302 overwriteAll(FileOperator::NONE),
310 memset(ignoreAll, false, sizeof(ignoreAll));
311 progressBar->setMaximum(0);
312 QFont barFont = progressBar->font();
313 barFont.setPointSize(12);
314 progressBar->setFont(barFont);
315 progressBar->setFormat(tr("Gathering information..."));
316 progressBar->setMinimumHeight(44);
317 progressBar->setStyle(new QPlastiqueStyle);
318 //progressBar->setStyle(new QMotifStyle);
322 FileManipulatorThread::~FileManipulatorThread() {
323 if (progressBar->value() < progressBar->maximum()) {
324 std::cout << "WARNING: deleting a progressbar which's value " << progressBar->value() <<
325 " has not reached maximum of " << progressBar->maximum() << std::endl;
331 void FileManipulatorThread::setResponse(
332 const FileOperator::Response response,
333 const bool applyToAll,
338 this->response = response;
341 if (response == FileOperator::KEEP
342 || response == FileOperator::OVERWRITE
343 || response == FileOperator::NONE)
345 overwriteAll = response;
348 if (response == FileOperator::IGNORE) {
349 ignoreAll[err] = true;
353 if (response == FileOperator::ABORT) abort = true;
360 void FileManipulatorThread::processFiles(const QFileInfoList &files) {
361 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
368 bool FileManipulatorThread::remove(QString &fileName, const bool doUpdates) {
369 return remove(QFileInfo(fileName), doUpdates);
373 bool FileManipulatorThread::remove(const QFileInfoList &files, const bool doUpdates) {
375 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
376 if (!remove(*it, doUpdates)) res = false;
383 bool FileManipulatorThread::remove(const QFileInfo &file, const bool doUpdates) {
384 std::cout << "DELETING " << file.absoluteFilePath().toStdString() << std::endl;
386 QString path = file.absoluteFilePath();
387 QFSFileEngine engine(path);
389 if (doUpdates) updateFile(path);
392 if (!remove(listDirFiles(path), doUpdates)) return false;
394 if (!listDirFiles(path).size()) {
395 ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
398 ERROR_PROMPT(!engine.remove(), tr("Error deleting file %1."), path)
401 if (!abort && doUpdates) updateProgress(1);
403 if (abort || response == FileOperator::IGNORE) return false;
408 void FileManipulatorThread::copy(const QFileInfo &file) {
409 std::cout << "COPYING " << file.absoluteFilePath().toStdString()
410 << " to " << dest.absolutePath().toStdString() << std::endl;
412 QString path(file.absoluteFilePath());
413 QString newPath(dest.absolutePath() + "/" + file.fileName());
414 QFSFileEngine engine(path);
415 QFSFileEngine newEngine(newPath);
416 QFileInfo newFile(newPath);
420 // hack to prevent asking about the same file if we already asked in the rename(...) function
421 if (overwriteAll == FileOperator::DONT_ASK_ONCE) {
422 overwriteAll = FileOperator::NONE;
424 OVERWRITE_PROMPT(file, newFile)
429 if (response == FileOperator::KEEP) {
430 updateProgress(fileSizeMap[path]);
435 FileOperator::Response overwriteResponse = response;
437 if (newFile.exists() && !newFile.isDir()) {
438 if(!remove(newPath)) {
439 updateProgress(fileSizeMap[path]);
442 newFile = QFileInfo(newPath);
445 if (!newFile.exists()) {
446 ERROR_PROMPT_XP(!engine.mkdir(newPath, false),
447 tr("Error creating directory %1."), newPath,
448 updateProgress(fileSizeMap[path]),
454 QDir destBackup = dest;
457 FileOperator::Response tmpResp = overwriteAll;
458 overwriteAll = overwriteResponse;
460 processFiles(listDirFiles(path));
462 overwriteAll = tmpResp;
464 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
465 tr("Error setting permissions for directory %1."), newPath)
471 ERROR_PROMPT_XP(engine.isSequential(),
472 tr("Cannot copy sequential file %1."), path,
473 updateProgress(fileSizeMap[path]),
476 if (newFile.exists() && newFile.isDir()) {
477 ERROR_PROMPT_XP(!remove(newPath),
478 tr("Cannot replace directory %1 due to previous errors."), newPath,
479 updateProgress(fileSizeMap[path]),
483 ERROR_PROMPT_XP(!engine.open(QIODevice::ReadOnly),
484 tr("Error reading file %1."), path,
485 updateProgress(fileSizeMap[path]),
489 while (!abort && !ignore) {
492 ERROR_PROMPT(!newEngine.open(QIODevice::WriteOnly | QIODevice::Truncate),
493 tr("Error writing file %1."), newPath)
495 if (abort || response == FileOperator::IGNORE) {
496 if (response == FileOperator::IGNORE) {
497 updateProgress(fileSizeMap[path] - fileValue);
504 char block[BLOCK_SIZE];
506 while ((bytes = engine.read(block, sizeof(block))) > 0) {
507 if (bytes == -1 || bytes != newEngine.write(block, bytes)) {
509 SHOW_ERROR_PROMPT(tr("Error while reading from file %1."), path);
511 SHOW_ERROR_PROMPT(tr("Error while writing to file %1."), newPath);
515 if (response == FileOperator::IGNORE) {
516 updateProgress(fileSizeMap[path] - fileValue);
519 updateProgress(-fileValue);
535 if (abort || ignore) {
538 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
539 tr("Error setting permissions for file %1."), newPath)
545 unsigned int FileManipulatorThread::calculateFileSize(const QFileInfoList &files,
549 unsigned int res = 0;
551 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
552 unsigned int size = 0;
555 size += calculateFileSize(listDirFiles(it->absoluteFilePath()), count, addSize);
562 size += ceil(static_cast<float>(it->size()) / BLOCK_SIZE);
564 fileSizeMap[it->absoluteFilePath()] = size;
578 QFileInfoList FileManipulatorThread::listDirFiles(const QString &dirPath) {
580 return dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden);
584 void FileManipulatorThread::setBarSize(unsigned int size) {
586 emit setBarSize(this, size);
590 void FileManipulatorThread::updateProgress(int value) {
593 emit updateProgress(this, value);
597 void FileManipulatorThread::updateFile(const QString &name) {
599 fileName = FileOperator::shortenPath(name);
600 emit updateProgress(this, 0);
604 void FileManipulatorThread::setText(int value) {
605 if (progressBar->value() + value > progressBar->maximum()) {
606 std::cout << "WARNING: exceeding progressbar maximum (" << progressBar->maximum()
607 << ") by " << value << std::endl;
610 time_t now = time(0);
611 if (lastTimeUpdate < now) {
612 lastTimeUpdate = now;
614 time_t elapsed = now - startTime;
615 time_t remaining = (time_t) ((float) elapsed / barValue * (barSize - barValue));
616 struct tm *ts = gmtime(&remaining);
618 if (remaining < 60) {
619 strftime(timeBuf, sizeof(timeBuf), "%Ss", ts);
620 } else if (remaining < 3600) {
621 strftime(timeBuf, sizeof(timeBuf), "%M:%S", ts);
623 strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", ts);
627 progressBar->setFormat(barText.arg(fileName) + "\n%p% ETA " + timeBuf);
628 progressBar->setValue(progressBar->value() + value);
632 DeleteThread::DeleteThread(const QFileInfoList &files) : FileManipulatorThread(files) {
633 barText = tr("deleting %1");
637 void DeleteThread::run() {
640 setBarSize(calculateFileSize(files, true));
649 void DeleteThread::perform(const QFileInfo &file) {
654 CopyThread::CopyThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
655 barText = tr("copying %1");
659 void CopyThread::run() {
662 setBarSize(calculateFileSize(files, false, true));
671 void CopyThread::perform(const QFileInfo &file) {
676 MoveThread::MoveThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
677 barText = tr("moving %1");
681 void MoveThread::run() {
691 void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
692 setBarSize(barSize + files.size());
694 for (int i = 0; i < files.size(); ++i) {
695 QString path = files[i].absoluteFilePath();
696 QFSFileEngine engine(path);
697 QString newPath = dest.absolutePath() + "/" + files[i].fileName();
701 OVERWRITE_PROMPT(files[i], QFileInfo(newPath))
703 if (response == FileOperator::KEEP) {
704 // TODO lets not remove the source for now, I'm not sure what is correct behavior
711 while (!abort && !engine.rename(newPath)) {
712 // source and target are on different partitions
713 // this should happen on the first file, unless some are skipped by overwrite prompt
714 // we calculate the actual file sizes, because from now on copy & remove takes over
715 if (errno == EXDEV) {
716 overwriteAll = response;
717 // hack: we already checked the first file we are sending to processFiles(...)
718 // so we don't want to ask about this one again
719 if (overwriteAll == FileOperator::NONE) overwriteAll = FileOperator::DONT_ASK_ONCE;
721 QFileInfoList remainingFiles = files.mid(i);
723 setBarSize(barValue + calculateFileSize(remainingFiles, true, true));
725 processFiles(remainingFiles);
727 barText = tr("deleting %1");
729 remove(remainingFiles, true);
731 // just to quit the loops, we are done
733 // the target is nonempty dir. lets call this recursively and rename the contents one by one
734 } else if (errno == ENOTEMPTY || errno == EEXIST) {
735 FileOperator::Response tmpResp = overwriteAll;
736 overwriteAll = response;
738 rename(listDirFiles(path), QDir(newPath));
741 overwriteAll = tmpResp;
743 ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
746 // source and target are nonmatching types(file and dir)
747 // remove the target and let it loop once again
748 } else if (errno == ENOTDIR || errno == EISDIR) {
749 if (!remove(newPath)) break;
751 SHOW_ERROR_PROMPT(tr("Error moving %1."), path)
753 if (response == FileOperator::IGNORE) {
765 void MoveThread::perform(const QFileInfo &file) {