You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libfm-qt-packaging/src/fileoperation.cpp

423 lines
13 KiB

/*
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "fileoperation.h"
#include "fileoperationdialog.h"
#include <QTimer>
#include <QElapsedTimer>
#include <QMessageBox>
#include <QDebug>
#include "core/deletejob.h"
#include "core/trashjob.h"
#include "core/untrashjob.h"
#include "core/filetransferjob.h"
#include "core/filechangeattrjob.h"
#include "utilities.h"
namespace Fm {
#define SHOW_DLG_DELAY 1000
FileOperation::FileOperation(Type type, Fm::FilePathList srcPaths, QObject* parent):
QObject(parent),
type_{type},
job_{nullptr},
dlg_{nullptr},
srcPaths_{std::move(srcPaths)},
uiTimer_(nullptr),
elapsedTimer_(nullptr),
lastElapsed_(0),
updateRemainingTime_(true),
autoDestroy_(true) {
switch(type_) {
case Copy:
job_ = new FileTransferJob(srcPaths_, FileTransferJob::Mode::COPY);
break;
case Move:
job_ = new FileTransferJob(srcPaths_, FileTransferJob::Mode::MOVE);
break;
case Link:
job_ = new FileTransferJob(srcPaths_, FileTransferJob::Mode::LINK);
break;
case Delete:
job_ = new Fm::DeleteJob(srcPaths_);
break;
case Trash:
job_ = new Fm::TrashJob(srcPaths_);
break;
case UnTrash:
job_ = new Fm::UntrashJob(srcPaths_);
break;
case ChangeAttr:
job_ = new Fm::FileChangeAttrJob(srcPaths_);
break;
default:
break;
}
if(job_) {
// automatically delete the job object when it's finished.
job_->setAutoDelete(true);
// new C++ jobs
connect(job_, &Fm::Job::finished, this, &Fm::FileOperation::onJobFinish);
connect(job_, &Fm::Job::cancelled, this, &Fm::FileOperation::onJobCancalled);
connect(job_, &Fm::Job::error, this, &Fm::FileOperation::onJobError, Qt::BlockingQueuedConnection);
connect(job_, &Fm::FileOperationJob::fileExists, this, &Fm::FileOperation::onJobFileExists, Qt::BlockingQueuedConnection);
// we block the job deliberately until we prepare to start (initiailize the timer) so we can calculate elapsed time correctly.
connect(job_, &Fm::FileOperationJob::preparedToRun, this, &Fm::FileOperation::onJobPrepared, Qt::BlockingQueuedConnection);
}
}
void FileOperation::disconnectJob() {
if(job_) {
disconnect(job_, &Fm::Job::finished, this, &Fm::FileOperation::onJobFinish);
disconnect(job_, &Fm::Job::cancelled, this, &Fm::FileOperation::onJobCancalled);
disconnect(job_, &Fm::Job::error, this, &Fm::FileOperation::onJobError);
disconnect(job_, &Fm::FileOperationJob::fileExists, this, &Fm::FileOperation::onJobFileExists);
disconnect(job_, &Fm::FileOperationJob::preparedToRun, this, &Fm::FileOperation::onJobPrepared);
}
}
FileOperation::~FileOperation() {
if(uiTimer_) {
uiTimer_->stop();
delete uiTimer_;
uiTimer_ = nullptr;
}
if(elapsedTimer_) {
delete elapsedTimer_;
elapsedTimer_ = nullptr;
}
}
void FileOperation::setDestination(Fm::FilePath dest) {
destPath_ = std::move(dest);
switch(type_) {
case Copy:
case Move:
case Link:
if(job_) {
static_cast<FileTransferJob*>(job_)->setDestDirPath(destPath_);
}
break;
default:
break;
}
}
void FileOperation::setDestFiles(FilePathList destFiles) {
switch(type_) {
case Copy:
case Move:
case Link:
if(job_) {
static_cast<FileTransferJob*>(job_)->setDestPaths(std::move(destFiles));
}
break;
default:
break;
}
}
void FileOperation::setChmod(mode_t newMode, mode_t newModeMask) {
if(job_) {
auto job = static_cast<FileChangeAttrJob*>(job_);
job->setFileModeEnabled(true);
job->setFileMode(newMode, newModeMask);
}
}
void FileOperation::setChown(uid_t uid, gid_t gid) {
if(job_) {
auto job = static_cast<FileChangeAttrJob*>(job_);
if(uid != INVALID_UID) {
job->setOwnerEnabled(true);
job->setOwner(uid);
}
if(gid != INVALID_GID) {
job->setGroupEnabled(true);
job->setGroup(gid);
}
}
}
void FileOperation::setRecursiveChattr(bool recursive) {
if(job_) {
auto job = static_cast<FileChangeAttrJob*>(job_);
job->setRecursive(recursive);
}
}
bool FileOperation::run() {
delete uiTimer_;
// run the job
uiTimer_ = new QTimer();
uiTimer_->start(SHOW_DLG_DELAY);
connect(uiTimer_, &QTimer::timeout, this, &FileOperation::onUiTimeout);
if(job_) {
job_->runAsync();
return true;
}
return false;
}
void FileOperation::cancel() {
if(job_) {
job_->cancel();
}
}
void FileOperation::onUiTimeout() {
if(dlg_) {
// estimate remaining time based on past history
if(job_) {
Fm::FilePath curFilePath = job_->currentFile();
// update progress bar
double finishedRatio = job_->progress();
if(finishedRatio > 0.0 && updateRemainingTime_) {
dlg_->setPercent(int(finishedRatio * 100));
std::uint64_t totalSize, totalCount, finishedSize, finishedCount;
job_->totalAmount(totalSize, totalCount);
job_->finishedAmount(finishedSize, finishedCount);
// only show data transferred if the job progress can be calculated by file size.
// for jobs not related to data transfer (for example: change attr, delete,...), hide the UI
if(job_->calcProgressUsingSize()) {
dlg_->setDataTransferred(finishedSize, totalSize);
}
else {
dlg_->setFilesProcessed(finishedCount, totalCount);
}
double remainRatio = 1.0 - finishedRatio;
gint64 remaining = elapsedTime() * (remainRatio / finishedRatio) / 1000;
// qDebug("etime: %llu, finished: %lf, remain:%lf, remaining secs: %llu",
// elapsedTime(), finishedRatio, remainRatio, remaining);
dlg_->setRemainingTime(remaining);
}
// update currently processed file
if(curFilePath_ != curFilePath) {
curFilePath_ = std::move(curFilePath);
// FIXME: make this cleaner
curFile = QString::fromUtf8(curFilePath_.toString().get());
dlg_->setCurFile(curFile);
}
}
// this timeout slot is called every 0.5 second.
// by adding this flag, we can update remaining time every 1 second.
updateRemainingTime_ = !updateRemainingTime_;
}
else {
showDialog();
}
}
void FileOperation::showDialog() {
if(!dlg_) {
dlg_ = new FileOperationDialog(this);
dlg_->setSourceFiles(srcPaths_);
if(destPath_) {
dlg_->setDestPath(destPath_);
}
if(curFile.isEmpty()) {
dlg_->setPrepared();
dlg_->setCurFile(curFile);
}
uiTimer_->setInterval(500); // change the interval of the timer
// now the timer is used to update current file display
dlg_->show();
}
}
void FileOperation::onJobFileExists(const FileInfo& src, const FileInfo& dest, Fm::FileOperationJob::FileExistsAction& response, FilePath& newDest) {
pauseElapsedTimer();
showDialog();
response = dlg_->askRename(src, dest, newDest);
resumeElapsedTimer();
}
void FileOperation::onJobCancalled() {
qDebug("file operation is cancelled!");
}
void FileOperation::onJobError(const GErrorPtr& err, Fm::Job::ErrorSeverity severity, Fm::Job::ErrorAction& response) {
pauseElapsedTimer();
showDialog();
response = Fm::Job::ErrorAction(dlg_->error(err.get(), severity));
resumeElapsedTimer();
}
void FileOperation::onJobPrepared() {
if(!elapsedTimer_) {
elapsedTimer_ = new QElapsedTimer();
elapsedTimer_->start();
}
if(dlg_) {
dlg_->setPrepared();
}
}
void FileOperation::onJobFinish() {
disconnectJob();
if(uiTimer_) {
uiTimer_->stop();
delete uiTimer_;
uiTimer_ = nullptr;
}
if(dlg_) {
dlg_->done(QDialog::Accepted);
delete dlg_;
dlg_ = nullptr;
}
Q_EMIT finished();
// special handling for trash job
if(type_ == Trash && !job_->isCancelled()) {
auto trashJob = static_cast<Fm::TrashJob*>(job_);
/* some files cannot be trashed because underlying filesystems don't support it. */
auto unsupportedFiles = trashJob->unsupportedFiles();
if(!unsupportedFiles.empty()) { /* delete them instead */
/* FIXME: parent window might be already destroyed! */
QWidget* parent = nullptr; // FIXME: currently, parent window is not set
if(QMessageBox::question(parent, tr("Error"),
tr("Some files cannot be moved to trash can because "
"the underlying file systems don't support this operation.\n"
"Do you want to delete them instead?")) == QMessageBox::Yes) {
deleteFiles(std::move(unsupportedFiles), false);
}
}
}
if(autoDestroy_) {
delete this;
}
}
// static
FileOperation* FileOperation::copyFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent) {
FileOperation* op = new FileOperation(FileOperation::Copy, std::move(srcFiles), parent);
op->setDestination(dest);
op->run();
return op;
}
//static
FileOperation *FileOperation::copyFiles(FilePathList srcFiles, FilePathList destFiles, QWidget *parent) {
qDebug("copy: %s -> %s", srcFiles[0].toString().get(), destFiles[0].toString().get());
FileOperation* op = new FileOperation(FileOperation::Copy, std::move(srcFiles), parent);
op->setDestFiles(std::move(destFiles));
op->run();
return op;
}
// static
FileOperation* FileOperation::moveFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent) {
FileOperation* op = new FileOperation(FileOperation::Move, std::move(srcFiles), parent);
op->setDestination(dest);
op->run();
return op;
}
//static
FileOperation *FileOperation::moveFiles(FilePathList srcFiles, FilePathList destFiles, QWidget *parent) {
FileOperation* op = new FileOperation(FileOperation::Move, std::move(srcFiles), parent);
op->setDestFiles(std::move(destFiles));
op->run();
return op;
}
//static
FileOperation* FileOperation::symlinkFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent) {
FileOperation* op = new FileOperation(FileOperation::Link, std::move(srcFiles), parent);
op->setDestination(dest);
op->run();
return op;
}
//static
FileOperation *FileOperation::symlinkFiles(FilePathList srcFiles, FilePathList destFiles, QWidget *parent) {
FileOperation* op = new FileOperation(FileOperation::Link, std::move(srcFiles), parent);
op->setDestFiles(std::move(destFiles));
op->run();
return op;
}
//static
FileOperation* FileOperation::deleteFiles(Fm::FilePathList srcFiles, bool prompt, QWidget* parent) {
if(prompt) {
int result = QMessageBox::warning(parent, tr("Confirm"),
tr("Do you want to delete the selected files?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if(result != QMessageBox::Yes) {
return nullptr;
}
}
FileOperation* op = new FileOperation(FileOperation::Delete, std::move(srcFiles));
op->run();
return op;
}
//static
FileOperation* FileOperation::trashFiles(Fm::FilePathList srcFiles, bool prompt, QWidget* parent) {
if(prompt) {
int result = QMessageBox::warning(parent, tr("Confirm"),
tr("Do you want to move the selected files to trash can?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if(result != QMessageBox::Yes) {
return nullptr;
}
}
FileOperation* op = new FileOperation(FileOperation::Trash, std::move(srcFiles));
op->run();
return op;
}
//static
FileOperation* FileOperation::unTrashFiles(Fm::FilePathList srcFiles, QWidget* parent) {
FileOperation* op = new FileOperation(FileOperation::UnTrash, std::move(srcFiles), parent);
op->run();
return op;
}
// static
FileOperation* FileOperation::changeAttrFiles(Fm::FilePathList srcFiles, QWidget* parent) {
//TODO
FileOperation* op = new FileOperation(FileOperation::ChangeAttr, std::move(srcFiles), parent);
op->run();
return op;
}
} // namespace Fm