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 "operationthread.h"
26 #define BLOCK_SIZE (256 * 1024)
31 emit operationPaused(); \
36 #define SHOW_ERROR_PROMPT(promptString, fileName) \
38 if (ignoreAll[errno]) { \
42 char *realBuf = buf; \
44 strcpy(buf, tr("File is sequential").toStdString().c_str()); \
46 realBuf = strerror_r(errno, buf, 255); \
48 emit showErrorPrompt(this, promptString + " " + realBuf + ".", fileName, errno); \
53 #define ERROR_PROMPT(operation, promptString, fileName) \
56 while (!abort && operation) { \
57 SHOW_ERROR_PROMPT(promptString, fileName) \
58 if (response == IGNORE) { \
66 #define SPECIAL_COPY_ERROR_PROMPT(operation, promptString, fileName) \
68 ERROR_PROMPT(operation, promptString, fileName) \
69 if (abort || response == IGNORE) { \
71 updateProgress(fileSizeMap[path]); \
72 removeExcludeFiles.insert(path); \
79 #define OVERWRITE_PROMPT(file, newFile) \
83 while (!abort && response == NONE && newFile.exists()) { \
84 if (overwriteAll != NONE) { \
85 response = overwriteAll; \
87 emit showOverwritePrompt(this, newFile.absoluteFilePath(), \
88 newFile.isDir() && file.isDir()); \
92 else if (response == NONE) { \
93 emit showInputFilenamePrompt(this, newFile, file.isDir()); \
95 if (newNameFromDialog.size()) { \
96 newFile.setFile(newNameFromDialog); \
101 if (response == ASK) response = NONE; \
105 OperationThread::OperationThread(const QFileInfoList files, QDir dest) :
116 memset(ignoreAll, false, sizeof(ignoreAll));
120 void OperationThread::setResponse(
121 const Response response,
122 const bool applyToAll,
127 this->response = response;
131 || response == OVERWRITE
134 overwriteAll = response;
137 if (response == IGNORE) {
138 ignoreAll[err] = true;
142 if (response == ABORT) abort = true;
149 void OperationThread::processFiles(const QFileInfoList &files) {
150 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
158 bool OperationThread::remove(QString &fileName, const bool doUpdates) {
159 return remove(QFileInfo(fileName), doUpdates);
163 bool OperationThread::remove(const QFileInfoList &files, const bool doUpdates) {
165 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
166 if (!remove(*it, doUpdates)) res = false;
174 bool OperationThread::remove(const QFileInfo &file, const bool doUpdates) {
175 QString path = file.absoluteFilePath();
177 if (removeExcludeFiles.contains(path)) {
178 if (doUpdates) updateProgress(1);
182 QFSFileEngine engine(path);
184 if (doUpdates) updateFile(path);
187 if (!remove(listDirFiles(path), doUpdates)) {
188 if (doUpdates) updateProgress(1);
192 if (!listDirFiles(path).size()) {
193 ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
196 ERROR_PROMPT(!engine.remove(), tr("Error deleting file %1."), path)
199 if (!abort && doUpdates) updateProgress(1);
202 if (abort || response == IGNORE) return false;
207 void OperationThread::copy(const QFileInfo &file) {
208 QString path(file.absoluteFilePath());
209 QFSFileEngine engine(path);
210 QFileInfo newFile(dest.absolutePath() + "/" + file.fileName());
214 // hack to prevent asking about the same file if we already asked in the rename(...) function
215 if (overwriteAll == DONT_ASK_ONCE) {
218 OVERWRITE_PROMPT(file, newFile)
221 QString newPath(newFile.absoluteFilePath());
222 QFSFileEngine newEngine(newPath);
228 // save the overwrite response, because the response variable will get ovewritten in remove(...)
229 Response overwriteResponse = response;
231 if (newFile.exists() && !newFile.isDir()) {
232 // overwriting a file, so check for KEEP and handle it
233 if (response == KEEP) {
234 updateProgress(fileSizeMap[path]);
235 removeExcludeFiles.insert(path);
239 // if it should not be kept, remove it and return on failure
240 if(!remove(newPath)) {
241 updateProgress(fileSizeMap[path]);
244 // create new info since we deleted the file - is it needed?
245 newFile = QFileInfo(newPath);
247 // overwriting a directory - response KEEP means to keep the files inside,
248 // SKIP_DIR means to skip the dir completely
249 if (response == SKIP_DIR) {
250 updateProgress(fileSizeMap[path]);
251 removeExcludeFiles.insert(path);
256 if (!newFile.exists()) {
257 SPECIAL_COPY_ERROR_PROMPT(!engine.mkdir(newPath, false),
258 tr("Error creating directory %1."), newPath)
261 // we've done the job with the dir, so update progress and recurse into the dir
264 // change the dest for the recursion
265 QDir destBackup = dest;
268 // and set overwriteAll to the response we got a while ago
269 // because it applies to the files inside the dir
270 Response tmpResp = overwriteAll;
271 overwriteAll = overwriteResponse;
273 processFiles(listDirFiles(path));
275 overwriteAll = tmpResp;
277 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
278 tr("Error setting permissions for directory %1."), newPath)
285 if (response == KEEP) {
286 updateProgress(fileSizeMap[path]);
287 removeExcludeFiles.insert(path);
291 SPECIAL_COPY_ERROR_PROMPT(checkSequentialFile(engine), tr("Cannot copy file %1."), path)
293 if (newFile.exists() && newFile.isDir()) {
294 SPECIAL_COPY_ERROR_PROMPT(!remove(newPath),
295 tr("Cannot replace directory %1 due to previous errors."), newPath)
298 SPECIAL_COPY_ERROR_PROMPT(!engine.open(QIODevice::ReadOnly), tr("Error reading file %1."), path)
300 bool ignore = false, newFileWritten = false;
301 while (!abort && !ignore) {
305 ERROR_PROMPT(!newEngine.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Unbuffered),
306 tr("Error writing file %1."), newPath)
308 if (abort || response == IGNORE) {
309 if (response == IGNORE) {
310 updateProgress(fileSizeMap[path]);
311 removeExcludeFiles.insert(path);
317 newFileWritten = true;
320 char block[BLOCK_SIZE];
323 while ((bytes = engine.read(block, sizeof(block))) > 0) {
325 SHOW_ERROR_PROMPT(tr("Error while reading from file %1."), path);
328 if (response == IGNORE) {
329 updateProgress(fileSizeMap[path] - fileValue);
330 removeExcludeFiles.insert(path);
333 updateProgress(-fileValue);
340 char *blockPointer = block;
341 while (bytes != (written = newEngine.write(blockPointer, bytes))) {
342 SHOW_ERROR_PROMPT(tr("Error while writing to file %1."), newPath);
344 if (response == IGNORE) {
345 updateProgress(fileSizeMap[path] - fileValue);
346 removeExcludeFiles.insert(path);
350 if (abort || ignore) break;
352 if (written == -1) written = 0;
354 blockPointer += written;
361 if (abort || ignore) break;
374 if (abort || ignore) {
375 if (newFileWritten) {
379 ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
380 tr("Error setting permissions for file %1."), newPath)
386 unsigned int OperationThread::calculateFileSize(const QFileInfoList &files,
390 unsigned int res = 0;
392 for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
393 unsigned int size = 0;
399 size += calculateFileSize(listDirFiles(it->absoluteFilePath()), count, addSize);
406 size += ceil(static_cast<float>(it->size()) / BLOCK_SIZE);
408 fileSizeMap[it->absoluteFilePath()] = size;
422 QFileInfoList OperationThread::listDirFiles(const QString &dirPath) {
424 return dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden);
428 void OperationThread::setTotalSize(unsigned int size) {
430 emit totalSizeChanged(size);
434 void OperationThread::updateProgress(int value) {
437 emit progressUpdate(value);
441 void OperationThread::updateFile(const QString &name) {
443 emit fileNameUpdated(shortenPath(name));
447 void OperationThread::waitOnCond() {
448 time_t waitTime = time(0);
449 waitCond.wait(&mutex);
450 emit operationResumed(time(0) - waitTime);
454 bool OperationThread::checkSequentialFile(const QFSFileEngine &engine) {
456 if (engine.isSequential()) {
457 if (!errno) errno = 255;
465 void OperationThread::wake() {
471 void DeleteThread::run() {
474 setTotalSize(calculateFileSize(files, true));
475 emit operationStarted(time(0));
484 void DeleteThread::perform(const QFileInfo &file) {
489 void CopyThread::run() {
492 setTotalSize(calculateFileSize(files, false, true));
493 emit operationStarted(time(0));
502 void CopyThread::perform(const QFileInfo &file) {
507 void MoveThread::run() {
517 void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
518 setTotalSize(totalSize + files.size());
519 emit operationStarted(time(0));
521 for (int i = 0; i < files.size(); ++i) {
522 QString path = files[i].absoluteFilePath();
523 QFSFileEngine engine(path);
524 QFileInfo newFile(dest.absolutePath() + "/" + files[i].fileName());
528 OVERWRITE_PROMPT(files[i], newFile)
530 // if we are owerwriting dir over a dir, we will get SKIP_DIR
531 // as a response from OVERWRITE_PROMT meaning we should skip it
532 // (KEEP would mean to keep the files inside)
533 if (files[i].isDir() && newFile.exists() && newFile.isDir()) {
534 if (response == SKIP_DIR) {
538 removeExcludeFiles.insert(path);
542 if (response == KEEP) {
546 removeExcludeFiles.insert(path);
551 QString newPath(newFile.absoluteFilePath());
552 QFSFileEngine newEngine(newPath);
556 while (!abort && !engine.rename(newPath)) {
557 // source and target are on different partitions
558 // this should happen on the first file, unless some are skipped by overwrite prompt
559 // we calculate the actual file sizes, because from now on copy & remove takes over
560 if (errno == EXDEV) {
561 overwriteAll = response;
562 // hack: we already checked the first file we are sending to processFiles(...)
563 // so we don't want to ask about this one again
564 if (overwriteAll == NONE) overwriteAll = DONT_ASK_ONCE;
566 QFileInfoList remainingFiles = files.mid(i);
568 setTotalSize(totalValue + calculateFileSize(remainingFiles, true, true));
570 processFiles(remainingFiles);
572 emit removeAfterCopy();
574 remove(remainingFiles, true);
578 // the target is nonempty dir. lets call this recursively and rename the contents one by one
579 } else if (errno == ENOTEMPTY || errno == EEXIST) {
580 Response tmpResp = overwriteAll;
581 overwriteAll = response;
583 rename(listDirFiles(path), QDir(newPath));
587 overwriteAll = tmpResp;
592 // source and target are nonmatching types(file and dir)
593 // remove the target and let it loop once again
594 } else if (errno == ENOTDIR || errno == EISDIR) {
595 if (!remove(newPath)) break;
597 SHOW_ERROR_PROMPT(tr("Error moving %1."), path)
599 if (response == IGNORE) {
615 void MoveThread::perform(const QFileInfo &file) {