diff --git a/krusader/Dialogs/checksumdlg.cpp b/krusader/Dialogs/checksumdlg.cpp index 977865c2..36d896c1 100644 --- a/krusader/Dialogs/checksumdlg.cpp +++ b/krusader/Dialogs/checksumdlg.cpp @@ -1,574 +1,574 @@ /***************************************************************************** * Copyright (C) 2005 Shie Erlich * * Copyright (C) 2007-2008 Csaba Karai * * Copyright (C) 2008 Jonas Bähr * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This package 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this package; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #include "checksumdlg.h" #include "../krglobal.h" #include "../krservices.h" #include "../krusader.h" #include "../GUI/krlistwidget.h" #include "../GUI/krtreewidget.h" // QtCore #include #include #include #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include void Checksum::startCreationWizard(const QString &path, const QStringList &files) { if (files.isEmpty()) return; QDialog *dialog = new CHECKSUM_::CreateWizard(path, files); dialog->show(); } void Checksum::startVerifyWizard(const QString &path, const QString &checksumFile) { QDialog *dialog = new CHECKSUM_::VerifyWizard(path, checksumFile); dialog->show(); } namespace CHECKSUM_ { bool stopListFiles; // async operation invoked by QtConcurrent::run in creation wizard QStringList listFiles(const QString &path, const QStringList &fileNames) { const QDir baseDir(path); QStringList allFiles; for (const QString fileName : fileNames) { if (stopListFiles) return QStringList(); QDir subDir = QDir(baseDir.filePath(fileName)); if (subDir.exists()) { subDir.setFilter(QDir::Files); QDirIterator it(subDir, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); while (it.hasNext()) { if (stopListFiles) return QStringList(); allFiles << baseDir.relativeFilePath(it.next()); } } else { // assume this is a file allFiles << fileName; } } return allFiles; } // ------------- Checksum Process ChecksumProcess::ChecksumProcess(QObject *parent, const QString &path) : KProcess(parent), m_tmpOutFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.stdout")), m_tmpErrFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.stderr")) { m_tmpOutFile.open(); // necessary to create the filename m_tmpErrFile.open(); // necessary to create the filename setOutputChannelMode(KProcess::SeparateChannels); // without this the next 2 lines have no effect! setStandardOutputFile(m_tmpOutFile.fileName()); setStandardErrorFile(m_tmpErrFile.fileName()); setWorkingDirectory(path); connect(this, &ChecksumProcess::errorOccurred, this, &ChecksumProcess::slotError); connect(this, static_cast(&QProcess::finished), this, &ChecksumProcess::slotFinished); } ChecksumProcess::~ChecksumProcess() { disconnect(this, 0, this, 0); // QProcess emits finished() on destruction close(); } void ChecksumProcess::slotError(QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { KMessageBox::error(0, i18n("Could not start %1.", program().join(" "))); } } void ChecksumProcess::slotFinished(int, QProcess::ExitStatus exitStatus) { if (exitStatus != QProcess::NormalExit) { KMessageBox::error(0, i18n("There was an error while running %1.", program().join(" "))); return; } // parse result files if (!KrServices::fileToStringList(&m_tmpOutFile, m_outputLines) || !KrServices::fileToStringList(&m_tmpErrFile, m_errorLines)) { KMessageBox::error(0, i18n("Error reading stdout or stderr")); return; } emit resultReady(); } // ------------- Generic Checksum Wizard ChecksumWizard::ChecksumWizard(const QString &path) : QWizard(krApp), m_path(path), m_process(0) { setAttribute(Qt::WA_DeleteOnClose); // init the dictionary - pity it has to be manually m_checksumTools.insert("md5", "md5sum"); m_checksumTools.insert("sha1", "sha1sum"); m_checksumTools.insert("sha256", "sha256sum"); m_checksumTools.insert("sha224", "sha224sum"); m_checksumTools.insert("sha384", "sha384sum"); m_checksumTools.insert("sha512", "sha512sum"); connect(this, &QWizard::currentIdChanged, this, &ChecksumWizard::slotCurrentIdChanged); } ChecksumWizard::~ChecksumWizard() { if (m_process) { delete m_process; } } void ChecksumWizard::slotCurrentIdChanged(int id) { if (id == m_introId) { onIntroPage(); } else if (id == m_progressId) { if (m_process) { // we are coming from the result page; delete m_process; m_process = 0; restart(); } else { button(QWizard::BackButton)->hide(); button(QWizard::NextButton)->hide(); onProgressPage(); } } else if (id == m_resultId) { onResultPage(); } } QWizardPage *ChecksumWizard::createProgressPage(const QString &title) { QWizardPage *page = new QWizardPage; page->setTitle(title); page->setPixmap(QWizard::LogoPixmap, krLoader->loadIcon("process-working", KIconLoader::Desktop, 32)); page->setSubTitle(i18n("Please wait...")); QVBoxLayout *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); // "busy" indicator QProgressBar *bar = new QProgressBar(); bar->setRange(0,0); mainLayout->addWidget(bar); return page; } bool ChecksumWizard::checkExists(const QString type) { if (!KrServices::cmdExist(m_checksumTools[type])) { KMessageBox::error( this, i18n("Krusader cannot find a checksum tool that handles %1 on your system. " "Please check the Dependencies page in Krusader's settings.", type)); return false; } return true; } void ChecksumWizard::runProcess(const QString &type, const QStringList &args) { Q_ASSERT(m_process == 0); m_process = new ChecksumProcess(this, m_path); m_process->setProgram(KrServices::fullPathName(m_checksumTools[type]), args); // show next page (with results) (only) when process is done connect(m_process, &ChecksumProcess::resultReady, this, &QWizard::next); // run the process m_process->start(); } void ChecksumWizard::addChecksumLine(KrTreeWidget *tree, const QString &line) { QTreeWidgetItem *item = new QTreeWidgetItem(tree); const int hashLength = line.indexOf(' '); // delimiter is either " " or " *" item->setText(0, line.left(hashLength)); QString fileName = line.mid(hashLength + 2); if (fileName.endsWith('\n')) fileName.chop(1); item->setText(1, fileName); } // ------------- Create Wizard CreateWizard::CreateWizard(const QString &path, const QStringList &_files) : ChecksumWizard(path), m_fileNames(_files), m_listFilesWatcher() { m_introId = addPage(createIntroPage()); m_progressId = addPage(createProgressPage(i18n("Creating Checksums"))); m_resultId = addPage(createResultPage()); setButton(QWizard::FinishButton, QDialogButtonBox(QDialogButtonBox::Save).button(QDialogButtonBox::Save)); connect(&m_listFilesWatcher, &QFutureWatcher::resultReadyAt, this, &CreateWizard::createChecksums); } QWizardPage *CreateWizard::createIntroPage() { QWizardPage *page = new QWizardPage; page->setTitle(i18n("Create Checksums")); page->setPixmap(QWizard::LogoPixmap, krLoader->loadIcon("document-edit-sign", KIconLoader::Desktop, 32)); page->setSubTitle(i18n("About to calculate checksum for the following files or directories:")); QVBoxLayout *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); // file list KrListWidget *listWidget = new KrListWidget; listWidget->addItems(m_fileNames); mainLayout->addWidget(listWidget); // checksum method QHBoxLayout *hLayout = new QHBoxLayout; QLabel *methodLabel = new QLabel(i18n("Select the checksum method:")); hLayout->addWidget(methodLabel); m_methodBox = new KComboBox; // -- fill the combo with available methods for (const QString type: m_checksumTools.keys()) m_methodBox->addItem(type); m_methodBox->setFocus(); hLayout->addWidget(m_methodBox); mainLayout->addLayout(hLayout); return page; } QWizardPage *CreateWizard::createResultPage() { QWizardPage *page = new QWizardPage; page->setTitle(i18n("Checksum Results")); QVBoxLayout *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); m_hashesTreeWidget = new KrTreeWidget(this); m_hashesTreeWidget->setAllColumnsShowFocus(true); m_hashesTreeWidget->setHeaderLabels(QStringList() << i18n("Hash") << i18n("File")); mainLayout->addWidget(m_hashesTreeWidget); m_errorLabel = new QLabel(i18n("Errors received:")); mainLayout->addWidget(m_errorLabel); m_errorListWidget = new KrListWidget; mainLayout->addWidget(m_errorListWidget); m_onePerFileBox = new QCheckBox(i18n("Save one checksum file for each source file")); m_onePerFileBox->setChecked(false); mainLayout->addWidget(m_onePerFileBox); return page; } void CreateWizard::onIntroPage() { button(QWizard::NextButton)->show(); } void CreateWizard::onProgressPage() { // first, get all files (recurse in directories) - async stopListFiles = false; // QFuture cannot cancel QtConcurrent::run - connect(this, &CreateWizard::finished, [=]() { stopListFiles = true; }); + connect(this, &CreateWizard::finished, this, [=]() { stopListFiles = true; }); QFuture listFuture = QtConcurrent::run(listFiles, m_path, m_fileNames); m_listFilesWatcher.setFuture(listFuture); } void CreateWizard::createChecksums() { const QString type = m_methodBox->currentText(); if (!checkExists(type)) { button(QWizard::BackButton)->show(); return; } const QStringList &allFiles = m_listFilesWatcher.result(); if (allFiles.isEmpty()) { KMessageBox::error(this, i18n("No files found")); button(QWizard::BackButton)->show(); return; } runProcess(type, allFiles); // set suggested filename m_suggestedFilePath = QDir(m_path).filePath( (m_fileNames.count() > 1 ? "checksum." : (m_fileNames[0] + '.')) + type); } void CreateWizard::onResultPage() { // hash tools display errors into stderr, so we'll use that to determine the result of the job const QStringList outputLines = m_process->stdOutput(); const QStringList errorLines = m_process->errOutput(); bool errors = !errorLines.isEmpty(); bool successes = !outputLines.isEmpty(); QWizardPage *page = currentPage(); page->setPixmap(QWizard::LogoPixmap, krLoader->loadIcon(errors || !successes ? "dialog-error" : "dialog-information", KIconLoader::Desktop, 32)); page->setSubTitle(errors || !successes ? i18n("Errors were detected while creating the checksums") : i18n("Checksums were created successfully")); m_hashesTreeWidget->clear(); m_hashesTreeWidget->setVisible(successes); if (successes) { for (const QString line : outputLines) addChecksumLine(m_hashesTreeWidget, line); //m_hashesTreeWidget->sortItems(1, Qt::AscendingOrder); } m_errorLabel->setVisible(errors); m_errorListWidget->setVisible(errors); m_errorListWidget->clear(); m_errorListWidget->addItems(errorLines); m_onePerFileBox->setEnabled(outputLines.size() > 1); button(QWizard::FinishButton)->setEnabled(successes); } bool CreateWizard::savePerFile() { const QString type = m_suggestedFilePath.mid(m_suggestedFilePath.lastIndexOf('.')); krApp->startWaiting(i18n("Saving checksum files..."), 0); for (const QString line : m_process->stdOutput()) { const QString filename = line.mid(line.indexOf(' ') + 2) + type; if (!saveChecksumFile(QStringList() << line, filename)) { KMessageBox::error(this, i18n("Errors occurred while saving multiple checksums. Stopping")); krApp->stopWait(); return false; } } krApp->stopWait(); return true; } bool CreateWizard::saveChecksumFile(const QStringList &data, const QString &filename) { QString filePath = filename.isEmpty() ? m_suggestedFilePath : filename; if (filename.isEmpty() || QFile::exists(filePath)) { filePath = QFileDialog::getSaveFileName(this, QString(), filePath); if (filePath.isEmpty()) return false; // user pressed cancel } QFile file(filePath); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); for (const QString line : data) stream << line << "\n"; file.close(); } if (file.error() != QFile::NoError) { KMessageBox::detailedError(this, i18n("Error saving file %1", filePath), file.errorString()); return false; } return true; } void CreateWizard::accept() { const bool saved = m_onePerFileBox->isChecked() ? savePerFile() : saveChecksumFile(m_process->stdOutput()); if (saved) QWizard::accept(); } // ------------- Verify Wizard VerifyWizard::VerifyWizard(const QString &path, const QString &inputFile) : ChecksumWizard(path) { m_checksumFile = isSupported(inputFile) ? inputFile : path; m_introId = addPage(createIntroPage()); // m_checksumFile must already be set m_progressId = addPage(createProgressPage(i18n("Verifying Checksums"))); m_resultId = addPage(createResultPage()); } void VerifyWizard::slotChecksumPathChanged(const QString &path) { m_hashesTreeWidget->clear(); button(QWizard::NextButton)->setEnabled(false); if (!isSupported(path)) return; m_checksumFile = path; // parse and display checksum file content; only for the user, parsed values are not used m_hashesTreeWidget->clear(); QFile file(m_checksumFile); if (file.open(QFile::ReadOnly)) { QTextStream inStream(&file); while (!inStream.atEnd()) { addChecksumLine(m_hashesTreeWidget, file.readLine()); } } file.close(); button(QWizard::NextButton)->setEnabled(true); } QWizardPage *VerifyWizard::createIntroPage() { QWizardPage *page = new QWizardPage; page->setTitle(i18n("Verify Checksum File")); page->setPixmap(QWizard::LogoPixmap, krLoader->loadIcon("document-edit-verify", KIconLoader::Desktop, 32)); page->setSubTitle(i18n("About to verify the following checksum file")); QVBoxLayout *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); // checksum file QHBoxLayout *hLayout = new QHBoxLayout; QLabel *checksumFileLabel = new QLabel(i18n("Checksum file:")); hLayout->addWidget(checksumFileLabel); KUrlRequester *checksumFileReq = new KUrlRequester; QString typesFilter; for (const QString ext: m_checksumTools.keys()) typesFilter += ("*." + ext + ' '); checksumFileReq->setFilter(typesFilter); checksumFileReq->setText(m_checksumFile); checksumFileReq->setFocus(); connect(checksumFileReq, &KUrlRequester::textChanged, this, &VerifyWizard::slotChecksumPathChanged); hLayout->addWidget(checksumFileReq); mainLayout->addLayout(hLayout); // content of checksum file m_hashesTreeWidget = new KrTreeWidget(page); m_hashesTreeWidget->setAllColumnsShowFocus(true); m_hashesTreeWidget->setHeaderLabels(QStringList() << i18n("Hash") << i18n("File")); mainLayout->addWidget(m_hashesTreeWidget); return page; } QWizardPage *VerifyWizard::createResultPage() { QWizardPage *page = new QWizardPage; page->setTitle(i18n("Verify Result")); QVBoxLayout *mainLayout = new QVBoxLayout; page->setLayout(mainLayout); m_outputLabel = new QLabel(i18n("Result output:")); mainLayout->addWidget(m_outputLabel); m_outputListWidget = new KrListWidget; mainLayout->addWidget(m_outputListWidget); return page; } void VerifyWizard::onIntroPage() { // cannot do this in constructor: NextButton->hide() is overridden slotChecksumPathChanged(m_checksumFile); } void VerifyWizard::onProgressPage() { // verify checksum file... const QString extension = QFileInfo(m_checksumFile).suffix(); if (!checkExists(extension)) { button(QWizard::BackButton)->show(); return; } runProcess(extension, QStringList() << "--strict" << "-c" << m_checksumFile); } void VerifyWizard::onResultPage() { // better not only trust error output const bool errors = m_process->exitCode() != 0 || !m_process->errOutput().isEmpty(); QWizardPage *page = currentPage(); page->setPixmap(QWizard::LogoPixmap, krLoader->loadIcon(errors ? "dialog-error" : "dialog-information", KIconLoader::Desktop, 32)); page->setSubTitle(errors ? i18n("Errors were detected while verifying the checksums") : i18n("Checksums were verified successfully")); // print everything, errors first m_outputListWidget->clear(); m_outputListWidget->addItems(m_process->errOutput() + m_process->stdOutput()); button(QWizard::FinishButton)->setEnabled(!errors); } bool VerifyWizard::isSupported(const QString &path) { const QFileInfo fileInfo(path); return fileInfo.isFile() && m_checksumTools.keys().contains(fileInfo.suffix()); } } // NAMESPACE CHECKSUM_ diff --git a/krusader/FileSystem/defaultfilesystem.cpp b/krusader/FileSystem/defaultfilesystem.cpp index 5d5897be..c588f649 100644 --- a/krusader/FileSystem/defaultfilesystem.cpp +++ b/krusader/FileSystem/defaultfilesystem.cpp @@ -1,385 +1,385 @@ /*************************************************************************** defaultfilesystem.cpp ------------------- copyright : (C) 2000 by Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net --------------------------------------------------------------------------- *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD S o u r c e F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "defaultfilesystem.h" // QtCore #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #include "../JobMan/krjob.h" DefaultFileSystem::DefaultFileSystem(): FileSystem(), _watcher() { _type = FS_DEFAULT; } void DefaultFileSystem::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode, bool showProgressInfo, JobMan::StartMode startMode) { // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::JobFlags flags = showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KrJob *krJob = KrJob::createCopyJob(mode, urls, destination, flags); - connect(krJob, &KrJob::started, [=](KIO::Job *job) { connectJob(job, dest); }); + connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJob(job, dest); }); if (mode == KIO::CopyJob::Move) { // notify source about removed files - connect(krJob, &KrJob::started, [=](KIO::Job *job) { connectSourceFileSystem(job, urls); }); + connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectSourceFileSystem(job, urls); }); } krJobMan->manageJob(krJob, startMode); } void DefaultFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) { // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::DropJob *job = KIO::drop(event, dest); // NOTE: DropJob does not provide information about the actual user choice // (move/copy/link/abort). We have to assume the worst (move) connectJob(job, dest); connectSourceFileSystem(job, KUrlMimeData::urlsFromMimeData(event->mimeData())); // NOTE: DrobJobs are internally recorded //recordJobUndo(job, type, dst, src); } void DefaultFileSystem::connectSourceFileSystem(KJob *job, const QList urls) { if (!urls.isEmpty()) { // NOTE: we assume that all files were in the same directory and only emit one signal for // the directory of the first file URL const QUrl url = urls.first().adjusted(QUrl::RemoveFilename); - connect(job, &KIO::Job::result, [=]() { emit fileSystemChanged(url); }); + connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(url); }); } } void DefaultFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, QString dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); const QString scheme = destination.scheme(); if (scheme == "tar" || scheme == "zip" || scheme == "krarc") { if (QDir(cleanUrl(destination).path()).exists()) // if we get out from the archive change the protocol destination.setScheme("file"); } } copyFiles(fileUrls, destination, mode); } void DefaultFileSystem::mkDir(const QString &name) { KIO::SimpleJob* job = KIO::mkdir(getUrl(name)); connectJob(job, currentDirectory()); } void DefaultFileSystem::rename(const QString &oldName, const QString &newName) { const QUrl oldUrl = getUrl(oldName); const QUrl newUrl = getUrl(newName); KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); connectJob(job, currentDirectory()); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); } QUrl DefaultFileSystem::getUrl(const QString& name) const { // NOTE: on non-local fs file URL does not have to be path + name! FileItem *fileItem = getFileItem(name); if (fileItem) return fileItem->getUrl(); QUrl absoluteUrl(_currentDirectory); absoluteUrl.setPath(absoluteUrl.path() + '/' + name); return absoluteUrl; } void DefaultFileSystem::updateFilesystemInfo() { if (!KConfigGroup(krConfig, "Look&Feel").readEntry("ShowSpaceInformation", true)) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("Space information disabled"), "", 0, 0); return; } if (!_currentDirectory.isLocalFile()) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("No space information on non-local filesystems"), "", 0, 0); return; } const QString path = _currentDirectory.path(); const KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(path); if (!info.isValid()) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("Space information unavailable"), "", 0, 0); return; } _mountPoint = info.mountPoint(); const KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(path); const QString fsType = mountPoint ? mountPoint->mountType() : ""; emit fileSystemInfoChanged("", fsType, info.size(), info.available()); } // ==== protected ==== bool DefaultFileSystem::refreshInternal(const QUrl &directory, bool showHidden) { if (!KProtocolManager::supportsListing(directory)) { emit error(i18n("Protocol not supported by Krusader:\n%1", directory.url())); return false; } delete _watcher; // stop watching the old dir if (directory.isLocalFile()) { // we could read local directories with KIO but using Qt is a lot faster! return refreshLocal(directory); } _currentDirectory = cleanUrl(directory); // start the listing job KIO::ListJob *job = KIO::listDir(_currentDirectory, KIO::HideProgressInfo, showHidden); connect(job, &KIO::ListJob::entries, this, &DefaultFileSystem::slotAddFiles); connect(job, &KIO::ListJob::redirection, this, &DefaultFileSystem::slotRedirection); connect(job, &KIO::ListJob::permanentRedirection, this, &DefaultFileSystem::slotRedirection); connect(job, &KIO::Job::result, this, &DefaultFileSystem::slotListResult); // ensure connection credentials are asked only once if(!parentWindow.isNull()) { KIO::JobUiDelegate *ui = static_cast(job->uiDelegate()); ui->setWindow(parentWindow); } emit refreshJobStarted(job); _listError = false; // ugly: we have to wait here until the list job is finished QEventLoop eventLoop; connect(job, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() return !_listError; } // ==== protected slots ==== void DefaultFileSystem::slotListResult(KJob *job) { if (job && job->error()) { // we failed to refresh _listError = true; emit error(job->errorString()); // display error message (in panel) } } void DefaultFileSystem::slotAddFiles(KIO::Job *, const KIO::UDSEntryList& entries) { for (const KIO::UDSEntry entry : entries) { FileItem *fileItem = FileSystem::createFileItemFromKIO(entry, _currentDirectory); if (fileItem) { addFileItem(fileItem); } } } void DefaultFileSystem::slotRedirection(KIO::Job *job, const QUrl &url) { krOut << "redirection to " << url; // some protocols (zip, tar) send redirect to local URL without scheme const QUrl newUrl = preferLocalUrl(url); if (newUrl.scheme() != _currentDirectory.scheme()) { // abort and start over again, // some protocols (iso, zip, tar) do this on transition to local fs job->kill(); _isRefreshing = false; refresh(newUrl); return; } _currentDirectory = cleanUrl(newUrl); } void DefaultFileSystem::slotWatcherDirty(const QString& path) { if (path == realPath()) { // this happens // 1. if a directory was created/deleted/renamed inside this directory. No deleted // 2. during and after a file operation (create/delete/rename/touch) inside this directory // KDirWatcher doesn't reveal the name of changed directories and we have to refresh. // (QFileSystemWatcher in Qt5.7 can't help here either) refresh(); return; } const QString name = QUrl::fromLocalFile(path).fileName(); FileItem *fileItem = getFileItem(name); if (!fileItem) { krOut << "dirty watcher file not found (unexpected): " << path; // this happens at least for cifs mounted filesystems: when a new file is created, a dirty // signal with its file path but no other signals are sent (buggy behaviour of KDirWatch) refresh(); return; } // we have an updated file.. FileItem *newFileItem = createLocalFileItem(name); *fileItem = *newFileItem; delete newFileItem; emit updatedFileItem(fileItem); } void DefaultFileSystem::slotWatcherDeleted(const QString& path) { if (path != realPath()) { // ignore deletion of files here, a 'dirty' signal will be send anyway return; } // the current directory was deleted, try a refresh, which will fail. An error message will // be emitted and the empty (non-existing) directory remains. refresh(); } bool DefaultFileSystem::refreshLocal(const QUrl &directory) { const QString path = KrServices::urlToLocalPath(directory); #ifdef Q_WS_WIN if (!path.contains("/")) { // change C: to C:/ path = path + QString("/"); } #endif // check if the new directory exists if (!QDir(path).exists()) { emit error(i18n("The folder %1 does not exist.", path)); return false; } // mount if needed emit aboutToOpenDir(path); // set the current directory... _currentDirectory = directory; _currentDirectory.setPath(QDir::cleanPath(_currentDirectory.path())); // Note: we are using low-level Qt functions here. // It's around twice as fast as using the QDir class. QT_DIR* dir = QT_OPENDIR(path.toLocal8Bit()); if (!dir) { emit error(i18n("Cannot open the folder %1.", path)); return false; } // change directory to the new directory const QString savedDir = QDir::currentPath(); if (!QDir::setCurrent(path)) { emit error(i18nc("%1=folder path", "Access to %1 denied", path)); QT_CLOSEDIR(dir); return false; } QT_DIRENT* dirEnt; QString name; const bool showHidden = showHiddenFiles(); while ((dirEnt = QT_READDIR(dir)) != NULL) { name = QString::fromLocal8Bit(dirEnt->d_name); // show hidden files? if (!showHidden && name.left(1) == ".") continue ; // we don't need the "." and ".." entries if (name == "." || name == "..") continue; FileItem* temp = createLocalFileItem(name); addFileItem(temp); } // clean up QT_CLOSEDIR(dir); QDir::setCurrent(savedDir); // start watching the new dir for file changes _watcher = new KDirWatch(this); // if the current dir is a link path the watcher needs to watch the real path - and signal // parameters will be the real path _watcher->addDir(realPath(), KDirWatch::WatchFiles); connect(_watcher.data(), &KDirWatch::dirty, this, &DefaultFileSystem::slotWatcherDirty); // NOTE: not connecting 'created' signal. A 'dirty' is send after that anyway //connect(_watcher, SIGNAL(created(QString)), this, SLOT(slotWatcherCreated(QString))); connect(_watcher.data(), &KDirWatch::deleted, this, &DefaultFileSystem::slotWatcherDeleted); _watcher->startScan(false); return true; } FileItem *DefaultFileSystem::createLocalFileItem(const QString &name) { return FileSystem::createLocalFileItem(name, _currentDirectory.path()); } QString DefaultFileSystem::DefaultFileSystem::realPath() { return QDir(_currentDirectory.toLocalFile()).canonicalPath(); } QUrl DefaultFileSystem::resolveRelativePath(const QUrl &url) { // if e.g. "/tmp/bin" is a link to "/bin", // resolve "/tmp/bin/.." to "/tmp" and not "/" return url.adjusted(QUrl::NormalizePathSegments); } diff --git a/krusader/FileSystem/filesystem.cpp b/krusader/FileSystem/filesystem.cpp index 0f4ee444..9a4aac37 100644 --- a/krusader/FileSystem/filesystem.cpp +++ b/krusader/FileSystem/filesystem.cpp @@ -1,298 +1,298 @@ /*************************************************************************** filesystem.cpp ------------------- copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net ------------------------------------------------------------------------ the filesystem class is an extendable class which by itself does (almost) nothing. other filesystems like the normal_filesystem inherits from this class and make it possible to use a consistent API for all types of filesystems. *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD S o u r c e F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "filesystem.h" // QtCore #include #include // QtWidgets #include #include #include #include #include "../defaults.h" #include "../krglobal.h" #include "../JobMan/jobman.h" #include "../JobMan/krjob.h" #include "krpermhandler.h" FileSystem::FileSystem() : DirListerInterface(0), _isRefreshing(false) {} FileSystem::~FileSystem() { clear(_fileItems); emit cleared(); // please don't remove this line. This informs the view about deleting the references } QList FileSystem::getUrls(const QStringList &names) const { QList urls; for (const QString name : names) { urls.append(getUrl(name)); } return urls; } FileItem *FileSystem::getFileItem(const QString &name) const { return _fileItems.contains(name) ? _fileItems.value(name) : 0; } QList FileSystem::searchFileItems(const KRQuery &filter) { QList result; for (FileItem *item : _fileItems.values()) { if (filter.match(item)) { result.append(item); } } return result; } KIO::filesize_t FileSystem::totalSize() const { KIO::filesize_t temp = 0; for (FileItem *item : _fileItems.values()) { if (!item->isDir() && item->getName() != "." && item->getName() != "..") { temp += item->getSize(); } } return temp; } QUrl FileSystem::ensureTrailingSlash(const QUrl &url) { if (url.path().endsWith('/')) { return url; } QUrl adjustedUrl(url); adjustedUrl.setPath(adjustedUrl.path() + '/'); return adjustedUrl; } QUrl FileSystem::preferLocalUrl(const QUrl &url){ if (url.isEmpty() || !url.scheme().isEmpty()) return url; QUrl adjustedUrl = url; adjustedUrl.setScheme("file"); return adjustedUrl; } bool FileSystem::refresh(const QUrl &directory) { if (_isRefreshing) { // NOTE: this does not happen (unless async)"; return false; } // workaround for krarc: find out if transition to local fs is wanted and adjust URL manually QUrl url = directory; if (_currentDirectory.scheme() == "krarc" && url.scheme() == "krarc" && QDir(url.path()).exists()) { url.setScheme("file"); } const bool dirChange = !url.isEmpty() && cleanUrl(url) != _currentDirectory; const QUrl toRefresh = dirChange ? url.adjusted(QUrl::NormalizePathSegments) : _currentDirectory; if (!toRefresh.isValid()) { emit error(i18n("Malformed URL:\n%1", toRefresh.toDisplayString())); return false; } _isRefreshing = true; FileItemDict tempFileItems(_fileItems); // old file items are still used during refresh _fileItems.clear(); if (dirChange) // show an empty directory while loading the new one and clear selection emit cleared(); const bool res = refreshInternal(toRefresh, showHiddenFiles()); _isRefreshing = false; if (!res) { // cleanup and abort if (!dirChange) emit cleared(); clear(tempFileItems); return false; } emit refreshDone(dirChange); clear(tempFileItems); updateFilesystemInfo(); return true; } void FileSystem::deleteFiles(const QStringList &fileNames, bool moveToTrash) { // get absolute URLs for file names const QList fileUrls = getUrls(fileNames); KrJob *krJob = KrJob::createDeleteJob(fileUrls, moveToTrash); - connect(krJob, &KrJob::started, [=](KIO::Job *job) { connectJob(job, currentDirectory()); }); + connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJob(job, currentDirectory()); }); if (moveToTrash) { // update destination: the trash bin (in case a panel/tab is showing it) - connect(krJob, &KrJob::started, [=](KIO::Job *job) { + connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { // Note: the "trash" protocal should always have only one "/" after the "scheme:" part - connect(job, &KIO::Job::result, [=]() { emit fileSystemChanged(QUrl("trash:/")); }); + connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(QUrl("trash:/")); }); }); } krJobMan->manageJob(krJob); } void FileSystem::connectJob(KJob *job, const QUrl &destination) { // (additional) direct refresh if on local fs because watcher is too slow const bool refresh = cleanUrl(destination) == _currentDirectory && isLocal(); connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, refresh); }); - connect(job, &KIO::Job::result, [=]() { emit fileSystemChanged(destination); }); + connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(destination); }); } bool FileSystem::showHiddenFiles() { const KConfigGroup gl(krConfig, "Look&Feel"); return gl.readEntry("Show Hidden", _ShowHidden); } FileItem *FileSystem::createLocalFileItem(const QString &name, const QString &directory, bool virt) { const QDir dir = QDir(directory); const QString path = dir.filePath(name); const QByteArray filePath = path.toLocal8Bit(); QT_STATBUF stat_p; stat_p.st_size = 0; stat_p.st_mode = 0; stat_p.st_mtime = 0; stat_p.st_uid = 0; stat_p.st_gid = 0; QT_LSTAT(filePath.data(), &stat_p); const KIO::filesize_t size = stat_p.st_size; bool isDir = S_ISDIR(stat_p.st_mode); const bool isLink = S_ISLNK(stat_p.st_mode); QString linkDestination; bool brokenLink = false; if (isLink) { // find where the link is pointing to // the path of the symlink target cannot be longer than the file size of the symlink char buffer[stat_p.st_size]; memset(buffer, 0, sizeof(buffer)); int bytesRead = readlink(filePath.data(), buffer, sizeof(buffer)); if (bytesRead != -1) { linkDestination = QString::fromLocal8Bit(buffer, bytesRead); // absolute or relative const QFileInfo linkFile(dir, linkDestination); if (!linkFile.exists()) brokenLink = true; else if (linkFile.isDir()) isDir = true; } else { krOut << "Failed to read link: " << path; } } return new FileItem(virt ? path : name, QUrl::fromLocalFile(path), isDir, size, stat_p.st_mode, stat_p.st_mtime, stat_p.st_uid, stat_p.st_gid, QString(), QString(), isLink, linkDestination, brokenLink); } FileItem *FileSystem::createFileItemFromKIO(const KIO::UDSEntry &entry, const QUrl &directory, bool virt) { const KFileItem kfi(entry, directory, true, true); const QString name = kfi.text(); // ignore un-needed entries if (name.isEmpty() || name == "." || name == "..") { return 0; } const QString localPath = kfi.localPath(); const QUrl url = !localPath.isEmpty() ? QUrl::fromLocalFile(localPath) : kfi.url(); const QString fname = virt ? url.toDisplayString() : name; // get file statistics... const time_t mtime = kfi.time(KFileItem::ModificationTime).toTime_t(); const mode_t mode = kfi.mode() | kfi.permissions(); // NOTE: we could get the mimetype (and file icon) from the kfileitem here but this is very // slow. Instead, the file item class has it's own (faster) way to determine the file type. // NOTE: "broken link" flag is always false, checking link destination existence is // considered to be too expensive return new FileItem(fname, url, kfi.isDir(), kfi.size(), mode, mtime, (uid_t) -1, (gid_t) -1, kfi.user(), kfi.group(), kfi.isLink(), kfi.linkDest(), false, kfi.ACL().asString(), kfi.defaultACL().asString()); } // ==== protected slots ==== void FileSystem::slotJobResult(KJob *job, bool refresh) { if (job->error() && job->uiDelegate()) { // show errors for modifying operations as popup (works always) job->uiDelegate()->showErrorMessage(); } if (refresh) { FileSystem::refresh(); } } // ==== private ==== void FileSystem::clear(FileItemDict &fileItems) { QHashIterator lit(fileItems); while (lit.hasNext()) { delete lit.next().value(); } fileItems.clear(); } diff --git a/krusader/FileSystem/virtualfilesystem.cpp b/krusader/FileSystem/virtualfilesystem.cpp index 9b24fa2c..0c18679b 100644 --- a/krusader/FileSystem/virtualfilesystem.cpp +++ b/krusader/FileSystem/virtualfilesystem.cpp @@ -1,345 +1,345 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This package 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this package; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #include "virtualfilesystem.h" // QtCore #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #define VIRTUALFILESYSTEM_DB "virtualfilesystem.db" QHash *> VirtualFileSystem::_virtFilesystemDict; QHash VirtualFileSystem::_metaInfoDict; VirtualFileSystem::VirtualFileSystem() : FileSystem() { if (_virtFilesystemDict.isEmpty()) { restore(); } _type = FS_VIRTUAL; } void VirtualFileSystem::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode /*mode*/, bool /*showProgressInfo*/, JobMan::StartMode /*startMode*/) { const QString dir = QDir(destination.path()).absolutePath().remove('/'); if (dir.isEmpty()) { showError(i18n("You cannot copy files directly to the 'virt:/' folder.\n" "You can create a sub folder and copy your files into it.")); return; } if (!_virtFilesystemDict.contains(dir)) { mkDirInternal(dir); } QList *urlList = _virtFilesystemDict[dir]; for (const QUrl &fileUrl : urls) { if (!urlList->contains(fileUrl)) { urlList->push_back(fileUrl); } } emit fileSystemChanged(QUrl("virt:///" + dir)); // may call refresh() } void VirtualFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) { const QList &urls = KUrlMimeData::urlsFromMimeData(event->mimeData()); // dropping on virtual filesystem is always copy operation copyFiles(urls, destination); } void VirtualFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode /*mode*/, QString dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); } copyFiles(fileUrls, destination); } void VirtualFileSystem::remove(const QStringList &fileNames) { const QString parentDir = currentDir(); if (parentDir == "/") { // remove virtual directory for (const QString &filename : fileNames) { _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + filename)); delete _virtFilesystemDict[filename]; _virtFilesystemDict.remove(filename); _metaInfoDict.remove(filename); } } else { // remove the URLs from the collection for (const QString name : fileNames) { if (_virtFilesystemDict.find(parentDir) != _virtFilesystemDict.end()) { QList *urlList = _virtFilesystemDict[parentDir]; urlList->removeAll(getUrl(name)); } } } emit fileSystemChanged(currentDirectory()); // will call refresh() } QUrl VirtualFileSystem::getUrl(const QString &name) const { FileItem *item = getFileItem(name); if (!item) { return QUrl(); // not found } return item->getUrl(); } void VirtualFileSystem::mkDir(const QString &name) { if (currentDir() != "/") { showError(i18n("Creating new folders is allowed only in the 'virt:/' folder.")); return; } mkDirInternal(name); emit fileSystemChanged(currentDirectory()); // will call refresh() } void VirtualFileSystem::rename(const QString &fileName, const QString &newName) { FileItem *item = getFileItem(fileName); if (!item) return; // not found if (currentDir() == "/") { // rename virtual directory _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + newName)); _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + fileName)); _virtFilesystemDict.insert(newName, _virtFilesystemDict.take(fileName)); refresh(); return; } // newName can be a (local) path or a full url QUrl dest(newName); if (dest.scheme().isEmpty()) dest.setScheme("file"); // add the new url to the list // the list is refreshed, only existing files remain - // so we don't have to worry if the job was successful _virtFilesystemDict[currentDir()]->append(dest); KIO::Job *job = KIO::moveAs(item->getUrl(), dest, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, false); }); - connect(job, &KIO::Job::result, [=]() { emit fileSystemChanged(currentDirectory()); }); + connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(currentDirectory()); }); } bool VirtualFileSystem::canMoveToTrash(const QStringList &fileNames) const { if (isRoot()) return false; for (const QString fileName : fileNames) { if (!getUrl(fileName).isLocalFile()) { return false; } } return true; } void VirtualFileSystem::setMetaInformation(const QString &info) { _metaInfoDict[currentDir()] = info; } // ==== protected ==== bool VirtualFileSystem::refreshInternal(const QUrl &directory, bool /*showHidden*/) { _currentDirectory = cleanUrl(directory); _currentDirectory.setHost(""); // remove invalid subdirectories _currentDirectory.setPath('/' + _currentDirectory.path().remove('/')); if (!_virtFilesystemDict.contains(currentDir())) { // NOTE: silently creating non-existing directories here. The search and locate tools expect // this. (And user can enter some directory and it will be created). mkDirInternal(currentDir()); save(); // infinite loop possible //emit fileSystemChanged(currentDirectory()); return true; } QList *urlList = _virtFilesystemDict[currentDir()]; const QString metaInfo = _metaInfoDict[currentDir()]; emit fileSystemInfoChanged(metaInfo.isEmpty() ? i18n("Virtual filesystem") : metaInfo, "", 0, 0); QMutableListIterator it(*urlList); while (it.hasNext()) { const QUrl url = it.next(); FileItem *item = createFileItem(url); if (!item) { // remove URL from the list for a file that no longer exists it.remove(); } else { addFileItem(item); } } save(); return true; } // ==== private ==== void VirtualFileSystem::mkDirInternal(const QString &name) { // clean path, consistent with currentDir() QString dirName = name; dirName = dirName.remove('/'); if (dirName.isEmpty()) dirName = '/'; _virtFilesystemDict.insert(dirName, new QList()); _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + dirName)); } void VirtualFileSystem::save() { KConfig *db = &VirtualFileSystem::getVirtDB(); db->deleteGroup("virt_db"); KConfigGroup group(db, "virt_db"); QHashIterator *> it(_virtFilesystemDict); while (it.hasNext()) { it.next(); QList *urlList = it.value(); QList::iterator url; QStringList entry; for (url = urlList->begin(); url != urlList->end(); ++url) { entry.append((*url).toDisplayString()); } // KDE 4.0 workaround: 'Item_' prefix is added as KConfig fails on 1 char names (such as /) group.writeEntry("Item_" + it.key(), entry); group.writeEntry("MetaInfo_" + it.key(), _metaInfoDict[it.key()]); } db->sync(); } void VirtualFileSystem::restore() { KConfig *db = &VirtualFileSystem::getVirtDB(); const KConfigGroup dbGrp(db, "virt_db"); const QMap map = db->entryMap("virt_db"); QMapIterator it(map); while (it.hasNext()) { it.next(); // KDE 4.0 workaround: check and remove 'Item_' prefix if (!it.key().startsWith(QLatin1String("Item_"))) continue; const QString key = it.key().mid(5); const QList urlList = KrServices::toUrlList(dbGrp.readEntry(it.key(), QStringList())); _virtFilesystemDict.insert(key, new QList(urlList)); _metaInfoDict.insert(key, dbGrp.readEntry("MetaInfo_" + key, QString())); } if (!_virtFilesystemDict["/"]) { // insert root element if missing for some reason _virtFilesystemDict.insert("/", new QList()); } } FileItem *VirtualFileSystem::createFileItem(const QUrl &url) { if (url.scheme() == "virt") { // return a virtual directory in root QString path = url.path().mid(1); if (path.isEmpty()) path = '/'; return FileItem::createVirtualDir(path, url); } const QUrl directory = url.adjusted(QUrl::RemoveFilename); if (url.isLocalFile()) { QFileInfo file(url.path()); return file.exists() ? FileSystem::createLocalFileItem(url.fileName(), directory.path(), true) : 0; } KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); connect(statJob, &KIO::Job::result, this, &VirtualFileSystem::slotStatResult); // ugly: we have to wait here until the stat job is finished QEventLoop eventLoop; connect(statJob, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() if (_fileEntry.count() == 0) { return 0; // stat job failed } if (!_fileEntry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)) { // TODO this also happens for FTP directories return 0; // file not found } return FileSystem::createFileItemFromKIO(_fileEntry, directory, true); } KConfig &VirtualFileSystem::getVirtDB() { //virtualfilesystem_db = new KConfig("data",VIRTUALFILESYSTEM_DB,KConfig::NoGlobals); static KConfig db(VIRTUALFILESYSTEM_DB, KConfig::CascadeConfig, QStandardPaths::AppDataLocation); return db; } void VirtualFileSystem::slotStatResult(KJob *job) { _fileEntry = job->error() ? KIO::UDSEntry() : static_cast(job)->statResult(); } void VirtualFileSystem::showError(const QString &error) { QWidget *window = QApplication::activeWindow(); KMessageBox::sorry(window, error); // window can be null, is allowed } diff --git a/krusader/JobMan/jobman.cpp b/krusader/JobMan/jobman.cpp index 0619db94..47a2b247 100644 --- a/krusader/JobMan/jobman.cpp +++ b/krusader/JobMan/jobman.cpp @@ -1,430 +1,430 @@ /***************************************************************************** * Copyright (C) 2016 Krusader Krew * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This package 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this package; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #include "jobman.h" // QtCore #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "krjob.h" #include "../krglobal.h" const int MAX_OLD_MENU_ACTIONS = 10; /** The menu action entry for a job in the popup menu.*/ class JobMenuAction : public QWidgetAction { Q_OBJECT public: JobMenuAction(KrJob *krJob, QObject *parent) : QWidgetAction(parent), m_krJob(krJob) { QWidget *container = new QWidget(); QGridLayout *layout = new QGridLayout(container); m_description = new QLabel(krJob->description()); m_progressBar = new QProgressBar(); layout->addWidget(m_description, 0, 0, 1, 3); layout->addWidget(m_progressBar, 1, 0); m_pauseResumeButton = new QPushButton(); updatePauseResumeButton(); connect(m_pauseResumeButton, &QPushButton::clicked, this, &JobMenuAction::slotPauseResumeButtonClicked); layout->addWidget(m_pauseResumeButton, 1, 1); m_cancelButton = new QPushButton(); m_cancelButton->setIcon(QIcon::fromTheme("remove")); m_cancelButton->setToolTip(i18n("Cancel Job")); connect(m_cancelButton, &QPushButton::clicked, this, &JobMenuAction::slotCancelButtonClicked); layout->addWidget(m_cancelButton, 1, 2); setDefaultWidget(container); connect(krJob, &KrJob::started, this, [=](KJob *job) { connect(job, &KJob::description, this, &JobMenuAction::slotDescription); connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(slotPercent(KJob*,ulong))); connect(job, &KJob::suspended, this, &JobMenuAction::updatePauseResumeButton); connect(job, &KJob::resumed, this, &JobMenuAction::updatePauseResumeButton); connect(job, &KJob::result, this, &JobMenuAction::slotResult); connect(job, &KJob::warning, this, [](KJob *, const QString &plain, const QString &) { krOut << "unexpected job warning: " << plain; }); updatePauseResumeButton(); }); connect(krJob, &KrJob::terminated, this, &JobMenuAction::slotTerminated); } bool isDone() { return !m_krJob; } protected slots: void slotDescription(KJob *, const QString &description, const QPair &field1, const QPair &field2) { const QPair textField = !field2.first.isEmpty() ? field2 : field1; QString text = description; if (!textField.first.isEmpty()) { text += QString(" - %1: %2").arg(textField.first, textField.second); } m_description->setText(text); if (!field2.first.isEmpty() && !field1.first.isEmpty()) { // NOTE: tooltips for QAction items in menu are not shown m_progressBar->setToolTip(QString("%1: %2").arg(field1.first, field1.second)); } } void slotPercent(KJob *, unsigned long percent) { m_progressBar->setValue(percent); } void updatePauseResumeButton() { m_pauseResumeButton->setIcon(QIcon::fromTheme( m_krJob->isRunning() ? "media-playback-pause" : m_krJob->isPaused() ? "media-playback-start" : "chronometer-start")); m_pauseResumeButton->setToolTip(m_krJob->isRunning() ? i18n("Pause Job") : m_krJob->isPaused() ? i18n("Resume Job") : i18n("Start Job")); } void slotResult(KJob *job) { // NOTE: m_job may already set to NULL now if(!job->error()) { // percent signal is not reliable, set manually m_progressBar->setValue(100); } } void slotTerminated() { m_pauseResumeButton->setEnabled(false); m_cancelButton->setIcon(QIcon::fromTheme("edit-clear")); m_cancelButton->setToolTip(i18n("Clear")); m_krJob = nullptr; } void slotPauseResumeButtonClicked() { if (!m_krJob) return; if (m_krJob->isRunning()) m_krJob->pause(); else m_krJob->start(); } void slotCancelButtonClicked() { if (m_krJob) { m_krJob->cancel(); } else { deleteLater(); } } private: KrJob *m_krJob; QLabel *m_description; QProgressBar *m_progressBar; QPushButton *m_pauseResumeButton; QPushButton *m_cancelButton; }; #include "jobman.moc" // required for class definitions with Q_OBJECT macro in implementation files const QString JobMan::sDefaultToolTip = i18n("No jobs"); JobMan::JobMan(QObject *parent) : QObject(parent), m_messageBox(0) { // job control action m_controlAction = new KToolBarPopupAction(QIcon::fromTheme("media-playback-pause"), i18n("Play/Pause &Job"), this); m_controlAction->setEnabled(false); connect(m_controlAction, &QAction::triggered, this, &JobMan::slotControlActionTriggered); QMenu *menu = new QMenu(krMainWindow); menu->setMinimumWidth(300); // make scrollable if menu is too long menu->setStyleSheet("QMenu { menu-scrollable: 1; }"); m_controlAction->setMenu(menu); // progress bar action m_progressBar = new QProgressBar(); m_progressBar->setToolTip(sDefaultToolTip); m_progressBar->setEnabled(false); // listen to clicks on progress bar m_progressBar->installEventFilter(this); QWidgetAction *progressAction = new QWidgetAction(krMainWindow); progressAction->setText(i18n("Job Progress Bar")); progressAction->setDefaultWidget(m_progressBar); m_progressAction = progressAction; // job queue mode action KConfigGroup cfg(krConfig, "JobManager"); m_queueMode = cfg.readEntry("Queue Mode", false); m_modeAction = new QAction(QIcon::fromTheme("media-playlist-repeat"), i18n("Job Queue Mode"), krMainWindow); m_modeAction->setToolTip(i18n("Run only one job in parallel")); m_modeAction->setCheckable(true); m_modeAction->setChecked(m_queueMode); - connect(m_modeAction, &QAction::toggled, [=](bool checked) mutable { + connect(m_modeAction, &QAction::toggled, this, [=](bool checked) mutable { m_queueMode = checked; cfg.writeEntry("Queue Mode", m_queueMode); }); // undo action KIO::FileUndoManager *undoManager = KIO::FileUndoManager::self(); undoManager->uiInterface()->setParentWidget(krMainWindow); m_undoAction = new QAction(QIcon::fromTheme("edit-undo"), i18n("Undo Last Job"), krMainWindow); m_undoAction->setEnabled(false); connect(m_undoAction, &QAction::triggered, undoManager, &KIO::FileUndoManager::undo); connect(undoManager, static_cast(&KIO::FileUndoManager::undoAvailable), m_undoAction, &QAction::setEnabled); connect(undoManager, &KIO::FileUndoManager::undoTextChanged, this, &JobMan::slotUndoTextChange); } bool JobMan::waitForJobs(bool waitForUserInput) { if (m_jobs.isEmpty() && !waitForUserInput) return true; // attempt to get all job threads does not work //QList threads = krMainWindow->findChildren(); m_autoCloseMessageBox = !waitForUserInput; m_messageBox = new QMessageBox(krMainWindow); m_messageBox->setWindowTitle(i18n("Warning")); m_messageBox->setIconPixmap(QIcon::fromTheme("dialog-warning") .pixmap(QMessageBox::standardIcon(QMessageBox::Information).size())); m_messageBox->setText(i18n("Are you sure you want to quit?")); m_messageBox->addButton(QMessageBox::Abort); m_messageBox->addButton(QMessageBox::Cancel); m_messageBox->setDefaultButton(QMessageBox::Cancel); for (KrJob *job: m_jobs) connect(job, &KrJob::terminated, this, &JobMan::slotUpdateMessageBox); slotUpdateMessageBox(); int result = m_messageBox->exec(); // blocking m_messageBox->deleteLater(); m_messageBox = 0; // accepted -> cancel all jobs if (result == QMessageBox::Abort) { for (KrJob *job: m_jobs) { job->cancel(); } return true; } // else: return false; } void JobMan::manageJob(KrJob *job, StartMode startMode) { JobMenuAction *menuAction = new JobMenuAction(job, m_controlAction); connect(menuAction, &QObject::destroyed, this, &JobMan::slotUpdateControlAction); m_controlAction->menu()->addAction(menuAction); cleanupMenu(); slotUpdateControlAction(); connect(job, &KrJob::started, this, &JobMan::slotKJobStarted); connect(job, &KrJob::terminated, this, &JobMan::slotTerminated); const bool enqueue = startMode == Enqueue || (startMode == Default && m_queueMode); if (startMode == Start || (startMode == Default && !m_queueMode) || (enqueue && !jobsAreRunning())) { job->start(); } m_jobs.append(job); updateUI(); } // #### protected slots void JobMan::slotKJobStarted(KJob *job) { // KJob has two percent() functions connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(slotPercent(KJob*,ulong))); connect(job, &KJob::description, this, &JobMan::slotDescription); connect(job, &KJob::suspended, this, &JobMan::updateUI); connect(job, &KJob::resumed, this, &JobMan::updateUI); } void JobMan::slotControlActionTriggered() { if (m_jobs.isEmpty()) { m_controlAction->menu()->clear(); m_controlAction->setEnabled(false); return; } const bool anyRunning = jobsAreRunning(); if (!anyRunning && m_queueMode) { m_jobs.first()->start(); } else { for (KrJob *job : m_jobs) { if (anyRunning) job->pause(); else job->start(); } } } void JobMan::slotPercent(KJob *, unsigned long) { updateUI(); } void JobMan::slotDescription(KJob*,const QString &description, const QPair &field1, const QPair &field2) { // TODO cache all descriptions if (m_jobs.length() > 1) return; m_progressBar->setToolTip( QString("%1\n%2: %3\n%4: %5") .arg(description, field1.first, field1.second, field2.first, field2.second)); } void JobMan::slotTerminated(KrJob *krJob) { m_jobs.removeAll(krJob); // NOTE: ignoring queue mode here. We assume that if queue mode is turned off, the user created // jobs which were not already started with a "queue" option and still wants queue behaviour. if (!m_jobs.isEmpty() && !jobsAreRunning()) { foreach (KrJob *job, m_jobs) { if (!job->isPaused()) { // start next job job->start(); break; } } } updateUI(); cleanupMenu(); } void JobMan::slotUpdateControlAction() { m_controlAction->setEnabled(!m_controlAction->menu()->isEmpty()); } void JobMan::slotUndoTextChange(const QString &text) { m_undoAction->setToolTip(KIO::FileUndoManager::self()->undoAvailable() ? text : i18n("Undo Last Job")); } void JobMan::slotUpdateMessageBox() { if (!m_messageBox) return; if (m_jobs.isEmpty() && m_autoCloseMessageBox) { m_messageBox->done(QMessageBox::Abort); return; } if (m_jobs.isEmpty()) { m_messageBox->setInformativeText(""); m_messageBox->setButtonText(QMessageBox::Abort, "Quit"); return; } m_messageBox->setInformativeText(i18np("There is one job operation left.", "There are %1 job operations left.", m_jobs.length())); m_messageBox->setButtonText(QMessageBox::Abort, "Abort Jobs and Quit"); } // #### private void JobMan::cleanupMenu() { const QList actions = m_controlAction->menu()->actions(); for (QAction *action : actions) { if (m_controlAction->menu()->actions().count() <= MAX_OLD_MENU_ACTIONS) break; JobMenuAction *jobAction = static_cast(action); if (jobAction->isDone()) { m_controlAction->menu()->removeAction(action); action->deleteLater(); } } } void JobMan::updateUI() { int totalPercent = 0; for (KrJob *job: m_jobs) { totalPercent += job->percent(); } const bool hasJobs = !m_jobs.isEmpty(); m_progressBar->setEnabled(hasJobs); if (hasJobs) { m_progressBar->setValue(totalPercent / m_jobs.length()); } else { m_progressBar->reset(); } if (!hasJobs) m_progressBar->setToolTip(i18n("No Jobs")); if (m_jobs.length() > 1) m_progressBar->setToolTip(i18np("%1 Job", "%1 Jobs", m_jobs.length())); const bool running = jobsAreRunning(); m_controlAction->setIcon(QIcon::fromTheme( !hasJobs ? "edit-clear" : running ? "media-playback-pause" : "media-playback-start")); m_controlAction->setToolTip(!hasJobs ? i18n("Clear Job List") : running ? i18n("Pause All Jobs") : i18n("Resume Job List")); } bool JobMan::jobsAreRunning() { for (KrJob *job: m_jobs) if (job->isRunning()) return true; return false; } diff --git a/krusader/JobMan/krjob.cpp b/krusader/JobMan/krjob.cpp index 15d9f98a..b597b44d 100644 --- a/krusader/JobMan/krjob.cpp +++ b/krusader/JobMan/krjob.cpp @@ -1,126 +1,126 @@ /***************************************************************************** * Copyright (C) 2016 Krusader Krew * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This package 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this package; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #include "krjob.h" #include #include #include KrJob *KrJob::createCopyJob(KIO::CopyJob::CopyMode mode, const QList &src, const QUrl &destination, KIO::JobFlags flags) { Type type; QString description; switch (mode) { case KIO::CopyJob::Copy: type = Copy; description = i18n("Copy to %1", destination.toDisplayString()); break; case KIO::CopyJob::Move: type = Move; description = i18n("Move to %1", destination.toDisplayString()); break; case KIO::CopyJob::Link: type = Link; description = i18n("Link to %1", destination.toDisplayString()); break; } return new KrJob(type, src, destination, flags, description); } KrJob *KrJob::createDeleteJob(const QList &urls, bool moveToTrash) { const Type type = moveToTrash ? Trash : Delete; const bool oneFile = urls.length() == 1; const QString description = moveToTrash ? (oneFile ? i18n("Move %1 to trash", urls.first().toDisplayString()) : i18np("Move %1 file to trash", "Move %1 files to trash", urls.length())) : (oneFile ? i18n("Delete %1", urls.first().toDisplayString()) : i18np("Delete %1 file", "Delete %1 files", urls.length())); return new KrJob(type, urls, QUrl(), KIO::DefaultFlags, description); } KrJob::KrJob(Type type, const QList &urls, const QUrl &dest, KIO::JobFlags flags, const QString &description) : m_type(type), m_urls(urls), m_dest(dest), m_flags(flags), m_description(description), m_job(0) { } void KrJob::start() { if (m_job) { // job was already started m_job->resume(); return; } switch (m_type) { case Copy: { KIO::CopyJob *job = KIO::copy(m_urls, m_dest, m_flags); KIO::FileUndoManager::self()->recordCopyJob(job); m_job = job; break; } case Move: { KIO::CopyJob *job = KIO::move(m_urls, m_dest, m_flags); KIO::FileUndoManager::self()->recordCopyJob(job); m_job = job; break; } case Link: { KIO::CopyJob *job = KIO::link(m_urls, m_dest, m_flags); KIO::FileUndoManager::self()->recordCopyJob(job); m_job = job; break; } case Trash: { m_job = KIO::trash(m_urls); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, m_urls, QUrl("trash:/"), m_job); break; } case Delete: m_job = KIO::del(m_urls); } - connect(m_job, &KIO::Job::finished, [=]() { + connect(m_job, &KIO::Job::finished, this, [=]() { emit terminated(this); deleteLater(); }); emit started(m_job); } void KrJob::cancel() { if (m_job) m_job->kill(); else { emit terminated(this); deleteLater(); } } void KrJob::pause() { if (m_job) m_job->suspend(); }