Working copy/move operation

This commit is contained in:
2025-09-14 20:31:45 +02:00
parent afd059b36b
commit 9cca183677
3 changed files with 416 additions and 15 deletions
+319 -2
View File
@@ -5,8 +5,213 @@
#include <QSettings>
#include <QStandardPaths>
#include <QDesktopServices>
#include <QMimeData>
#include <QClipboard>
#include <QThread>
#include "loadimage.h"
class FileTimes
{
public:
explicit FileTimes(const QString &path)
{
QFile file(path);
#ifndef Q_OS_WIN
birthTime = file.fileTime(QFileDevice::FileBirthTime);
#endif
modificationTime = file.fileTime(QFileDevice::FileModificationTime);
accessTime = file.fileTime(QFileDevice::FileAccessTime);
}
void apply(const QString &path)
{
QFile file(path);
if(file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::ExistingOnly))
{
#ifndef Q_OS_WIN // Only windows allow changing birth time
file.setFileTime(birthTime, QFileDevice::FileBirthTime);
#endif
file.setFileTime(accessTime, QFileDevice::FileAccessTime);
file.setFileTime(modificationTime, QFileDevice::FileModificationTime);
}
}
private:
QDateTime birthTime;
QDateTime modificationTime;
QDateTime accessTime;
};
FileTransfer::FileTransfer(FileManager *fm) :
_fm(fm)
{
}
FileTransfer::~FileTransfer()
{
_run = false;
}
void FileTransfer::copy(const QStringList &src, const QString &dst)
{
_run = true;
perform(src, dst, true);
emit finished();
}
void FileTransfer::move(const QStringList &src, const QString &dst)
{
_run = true;
perform(src, dst, false);
emit finished();
}
void FileTransfer::cancel()
{
_run = false;
}
void FileTransfer::perform(const QStringList &src, const QString &dst, bool copy)
{
QDir dstDir(dst);
if(!dstDir.exists())
{
emit error(tr("Error"), tr("Destination directory %1 doesn't exists").arg(dstDir.absolutePath()));
return;
}
QList<Action> actions;
QStringList dirs;
emit progress(0);
for(const QString &i : src)
{
QFileInfo srcInfo(i);
if(srcInfo.absolutePath() == dst || dst.startsWith(srcInfo.absoluteFilePath()))
return;
if(srcInfo.isDir())
{
QDir srcDir(i);
//qDebug() << "dir" << srcInfo.absoluteFilePath() << srcInfo.fileName();
if(!copy && !dstDir.exists(srcInfo.fileName()))
{
if(QFile::rename(srcInfo.absoluteFilePath(), dstDir.absoluteFilePath(srcInfo.fileName())))
continue;
}
actions.append({srcInfo.absoluteFilePath(), srcInfo.fileName(), true});
if(!copy)dirs.prepend(srcInfo.absoluteFilePath());
QDirIterator it(i, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
while(it.hasNext())
{
QFileInfo info = it.nextFileInfo();
if(info.fileName() == "." || info.fileName() == "..")
continue;
QString relativePath = srcDir.dirName() + "/" + srcDir.relativeFilePath(info.absoluteFilePath());
if(info.isDir())
{
actions.append({"", relativePath, true});
if(!copy)dirs.prepend(info.absoluteFilePath());
//qDebug() << "dir" << info.absoluteFilePath() << relativePath;
}
else
{
actions.append({info.absoluteFilePath(), dstDir.absoluteFilePath(relativePath), false});
//qDebug() << "file" << info.absoluteFilePath() << dstDir.absoluteFilePath(relativePath);
}
}
}
else
{
actions.append({srcInfo.absoluteFilePath(), dstDir.absoluteFilePath(srcInfo.fileName())});
//qDebug() << "file" << srcInfo.absoluteFilePath() << dstDir.absoluteFilePath(srcInfo.fileName());
}
}
bool overwriteAll = false;
bool skipAll = false;
int total = actions.size();
int i = 0;
for(auto &a : actions)
{
if(!_run)
return;
if(a.dir)
{
dstDir.mkpath(a.dst);
}
else
{
QFileInfo dstInfo(a.dst);
if(dstInfo.exists())
{
if(overwriteAll)
{
QFile::remove(dstInfo.absoluteFilePath());
}
else if(skipAll)
{
emit progress(i++ * 100 / total);
continue;
}
else
{
QMessageBox::StandardButton ret;
QMetaObject::invokeMethod(_fm, "overwrite", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QMessageBox::StandardButton, ret), Q_ARG(QString, dstInfo.fileName()));
switch(ret)
{
case QMessageBox::YesToAll:
overwriteAll = true;//break; is intentionally missing
case QMessageBox::Yes:
QFile::remove(dstInfo.absoluteFilePath());
break;
case QMessageBox::NoToAll:
skipAll = true;//break; is intentionally missing
case QMessageBox::No:
emit progress(i++ * 100 / total);
continue;
break;
case QMessageBox::Cancel:
return;
default:
break;
}
}
}
FileTimes t(a.src);
if(copy)
{
if(!QFile::copy(a.src, a.dst))
{
emit error(tr("Copy failed"), tr("Failed to copy file %1 to %2").arg(a.src).arg(a.dst));
return;
}
}
else
{
if(!QFile::rename(a.src, a.dst))
{
emit error(tr("Move failed"), tr("Failed to move file %1 to %2").arg(a.src).arg(a.dst));
return;
}
}
t.apply(a.dst);
}
emit progress(i++ * 100 / total);
}
if(!copy)
{
for(const QString &d : dirs)
{
QDir dir(d);
if(dir.isEmpty())
dir.removeRecursively();
}
}
}
PathTabBar::PathTabBar(const QStringList &tabs) :
_tabs(tabs)
{
@@ -147,6 +352,8 @@ FileManager::FileManager(const QSet<QString> &openFilter, QWidget *parent) : QMa
connect(ui->rightTab, &DirView::dirChanged, _rightTabBar, &PathTabBar::pathChanged);
connect(ui->leftTab, &DirView::openFile, this, &FileManager::openFile);
connect(ui->rightTab, &DirView::openFile, this, &FileManager::openFile);
connect(ui->leftTab, &DirView::filesAction, this, &FileManager::copyMoveFiles, Qt::QueuedConnection);
connect(ui->rightTab, &DirView::filesAction, this, &FileManager::copyMoveFiles, Qt::QueuedConnection);
connect(ui->actionLoad_FITS_keywordsLeft, &QAction::toggled, ui->leftTab, &DirView::loadFitsKeywords);
connect(ui->actionLoad_FITS_keywordsRight, &QAction::toggled, ui->rightTab, &DirView::loadFitsKeywords);
@@ -165,6 +372,8 @@ FileManager::FileManager(const QSet<QString> &openFilter, QWidget *parent) : QMa
connect(ui->actionSelect_columnsLeft, &QAction::triggered, this, &FileManager::selectFITSKeywords);
connect(ui->actionSelect_columnsRight, &QAction::triggered, this, &FileManager::selectFITSKeywords);
connect(ui->actionCopySelectedFilesPathsLeft, &QAction::triggered, this, &FileManager::copySelectedFilesPaths);
connect(ui->actionCopySelectedFilesPathsRight, &QAction::triggered, this, &FileManager::copySelectedFilesPaths);
connect(ui->leftPath, &QLineEdit::returnPressed, this, &FileManager::pathEdited);
connect(ui->rightPath, &QLineEdit::returnPressed, this, &FileManager::pathEdited);
@@ -175,6 +384,25 @@ FileManager::FileManager(const QSet<QString> &openFilter, QWidget *parent) : QMa
ui->menuLeft_Tab->addAction(drive.absoluteFilePath(), [path, this](){ ui->leftTab->setDir(path); });
ui->menuRight_Tab->addAction(drive.absoluteFilePath(), [path, this](){ ui->rightTab->setDir(path); });
}
ui->progressBar->hide();
ui->cancelButton->hide();
_thread = new QThread(this);
_thread->start();
_fileTransfer = new FileTransfer(this);
_fileTransfer->moveToThread(_thread);
connect(_fileTransfer, &FileTransfer::progress, ui->progressBar, &QProgressBar::setValue);
connect(_fileTransfer, &FileTransfer::error, this, &FileManager::errorMessage);
connect(_fileTransfer, &FileTransfer::finished, [this](){
ui->leftTab->setDragEnabled(true);
ui->rightTab->setDragEnabled(true);
ui->progressBar->hide();
ui->cancelButton->hide();
});
connect(this, &FileManager::copy, _fileTransfer, &FileTransfer::copy);
connect(this, &FileManager::move, _fileTransfer, &FileTransfer::move);
connect(ui->cancelButton, &QPushButton::clicked, [this](){ _fileTransfer->cancel(); });
}
FileManager::~FileManager()
@@ -190,6 +418,12 @@ FileManager::~FileManager()
settings.setValue("filemanager/rightLoadFitsKeywords", ui->actionLoad_FITS_keywordsRight->isChecked());
settings.setValue("filemanager/geometry", saveGeometry());
delete ui;
_fileTransfer->cancel();
_thread->quit();
_thread->wait();
delete _fileTransfer;
}
void FileManager::selectFITSKeywords()
@@ -213,6 +447,14 @@ void FileManager::selectFITSKeywords()
}
}
void FileManager::copySelectedFilesPaths()
{
if(sender() == ui->actionCopySelectedFilesPathsLeft)
ui->leftTab->copySelectedFilesPathsToClipboard();
if(sender() == ui->actionCopySelectedFilesPathsRight)
ui->rightTab->copySelectedFilesPathsToClipboard();
}
void FileManager::pathEdited()
{
if(sender() == ui->leftPath)
@@ -229,6 +471,40 @@ void FileManager::pathEdited()
}
}
void FileManager::copyMoveFiles(Qt::DropAction action, const QStringList &src, const QString &dst)
{
ui->leftTab->setDragEnabled(false);
ui->rightTab->setDragEnabled(false);
ui->progressBar->show();
ui->cancelButton->show();
switch(action)
{
case Qt::CopyAction:
emit copy(src, dst);
break;
case Qt::MoveAction:
emit move(src, dst);
break;
case Qt::LinkAction:
default:
break;
}
}
QMessageBox::StandardButton FileManager::overwrite(const QString &dst)
{
QMessageBox::StandardButton button = QMessageBox::question(this, tr("Overwrite file?"), tr("Destination file %1 already exists. Overwrite?").arg(dst),
QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel);
return button;
}
void FileManager::errorMessage(const QString &title, const QString &text)
{
QMessageBox::critical(this, title, text);
}
QCache<QString, ImageInfoData>* DirFileSystemModel::getCacheInstance()
{
static bool init = true;
@@ -241,7 +517,8 @@ QCache<QString, ImageInfoData>* DirFileSystemModel::getCacheInstance()
return &cache;
}
DirFileSystemModel::DirFileSystemModel(QObject *parent) : QFileSystemModel(parent)
DirFileSystemModel::DirFileSystemModel(QWidget *parentWidget) : QFileSystemModel(parentWidget)
,_parentWidget(parentWidget)
{
_cache = getCacheInstance();
setFilter(QDir::AllEntries | QDir::NoDot);
@@ -272,7 +549,9 @@ const QStringList &DirFileSystemModel::FITSKeywords() const
Qt::ItemFlags DirFileSystemModel::flags(const QModelIndex &index) const
{
return QFileSystemModel::flags(index) & ~Qt::ItemIsEditable;
Qt::ItemFlags ret = QFileSystemModel::flags(index) & ~Qt::ItemIsEditable;
if(index.row() == 0)ret &= ~Qt::ItemIsDragEnabled;
return ret;
}
int DirFileSystemModel::columnCount(const QModelIndex &parent) const
@@ -335,6 +614,21 @@ bool DirFileSystemModel::hasChildren(const QModelIndex &parent) const
return QFileSystemModel::hasChildren(parent);
}
bool DirFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_UNUSED(row);
Q_UNUSED(column);
if(data->hasUrls())
{
QStringList srcPaths;
for(auto &url : data->urls())
srcPaths.append(url.toLocalFile());
emit filesAction(action, srcPaths, filePath(parent));
}
return false;
}
void DirFileSystemModel::loadFitsKeywords(bool enable)
{
_loadFitsKeywords = enable;
@@ -348,6 +642,8 @@ DirView::DirView(QWidget *parent) : QTreeView(parent)
setDragEnabled(true);
setAcceptDrops(true);
connect(_dirFileSystemModel, &DirFileSystemModel::filesAction, this, &DirView::filesAction);
setModel(_dirFileSystemModel);
setSelectionMode(QAbstractItemView::ExtendedSelection);
@@ -435,3 +731,24 @@ void DirView::loadFitsKeywords(bool enable)
{
_dirFileSystemModel->loadFitsKeywords(enable);
}
void DirView::copySelectedFilesPathsToClipboard() const
{
QList<QUrl> urls;
QString text;
auto selected = selectionModel()->selectedRows();
for(auto &item : selected)
{
if(item.column() == 0)
{
QString path = _dirFileSystemModel->filePath(item);
text.append(path); text.append('\n');
urls.append(QUrl::fromLocalFile(path));
}
}
QClipboard *clipboard = QApplication::clipboard();
QMimeData *mimeData = new QMimeData();
mimeData->setUrls(urls);
mimeData->setText(text);
clipboard->setMimeData(mimeData);
}