From 158a93f7793bc2d3505bbcfbddedfa525b51866b Mon Sep 17 00:00:00 2001 From: Anthony Geay Date: Wed, 11 Oct 2017 10:16:54 +0200 Subject: [PATCH] RemoteFileBrowser to ease file transfert between host and cluster --- tools/CMakeLists.txt | 7 + tools/RemoteFileBrowser/CMakeLists.txt | 82 ++ tools/RemoteFileBrowser/QMachineBrowser | 77 ++ tools/RemoteFileBrowser/QMachineBrowser.cxx | 210 +++++ tools/RemoteFileBrowser/QRemoteCopyWidget | 128 +++ tools/RemoteFileBrowser/QRemoteCopyWidget.cxx | 402 ++++++++ tools/RemoteFileBrowser/QRemoteFileBrowser | 302 ++++++ .../RemoteFileBrowser/QRemoteFileBrowser.cxx | 892 ++++++++++++++++++ tools/RemoteFileBrowser/remotefilebrowser.cxx | 30 + 9 files changed, 2130 insertions(+) create mode 100644 tools/RemoteFileBrowser/CMakeLists.txt create mode 100644 tools/RemoteFileBrowser/QMachineBrowser create mode 100644 tools/RemoteFileBrowser/QMachineBrowser.cxx create mode 100644 tools/RemoteFileBrowser/QRemoteCopyWidget create mode 100644 tools/RemoteFileBrowser/QRemoteCopyWidget.cxx create mode 100644 tools/RemoteFileBrowser/QRemoteFileBrowser create mode 100644 tools/RemoteFileBrowser/QRemoteFileBrowser.cxx create mode 100644 tools/RemoteFileBrowser/remotefilebrowser.cxx diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index f07014dad..185c427ba 100755 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -45,3 +45,10 @@ IF(SALOME_USE_PYVIEWER) ADD_SUBDIRECTORY(PyEditor) ENDIF(SALOME_USE_PYVIEWER) +IF(COMPILER_SUPPORTS_CXX11) + SET(TOOLS_EXPORT_NAME ${PROJECT_NAME}) + SET(REMOTEFILEBROWSER_INSTALL_BINS "${SALOME_INSTALL_BINS}" CACHE PATH "") + SET(REMOTEFILEBROWSER_INSTALL_LIBS "${SALOME_INSTALL_LIBS}" CACHE PATH "") + SET(REMOTEFILEBROWSER_INSTALL_HEADERS "${SALOME_INSTALL_HEADERS}" CACHE PATH "") + ADD_SUBDIRECTORY(RemoteFileBrowser) +ENDIF(COMPILER_SUPPORTS_CXX11) diff --git a/tools/RemoteFileBrowser/CMakeLists.txt b/tools/RemoteFileBrowser/CMakeLists.txt new file mode 100644 index 000000000..28b51be82 --- /dev/null +++ b/tools/RemoteFileBrowser/CMakeLists.txt @@ -0,0 +1,82 @@ +# Copyright (C) 2017 CEA/DEN, EDF R&D +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +# Author : Anthony GEAY (EDF R&D) + +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8 FATAL_ERROR) +PROJECT(RemoteFileBrowser CXX) + +CMAKE_POLICY(SET CMP0003 NEW) + +SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake files") +IF(EXISTS ${CONFIGURATION_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake") + INCLUDE(SalomeMacros) +ELSE() + MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !") +ENDIF() + +SET(REMOTEFILEBROWSER_INSTALL_BINS bin CACHE PATH "Install path: RemoteFileBrowser binaries") +SET(REMOTEFILEBROWSER_INSTALL_LIBS lib CACHE PATH "Install path: RemoteFileBrowser libraries") +SET(REMOTEFILEBROWSER_INSTALL_HEADERS include CACHE PATH "Install path: RemoteFileBrowser headers") + +INCLUDE(SalomeSetupPlatform) +SET(BUILD_SHARED_LIBS TRUE) +INCLUDE(SalomeMacros) +FIND_PACKAGE(SalomeQt5 REQUIRED) +INCLUDE(UseQtExt) +INCLUDE_DIRECTORIES( + ${QT_INCLUDES} + ) + +ADD_DEFINITIONS( + ${QT_DEFINITIONS} + ) + +SET(qremotefilebrowser_SOURCES + QRemoteFileBrowser.cxx + QMachineBrowser.cxx + QRemoteCopyWidget.cxx + ) + +SET(qremotefilebrowser_HEADERS + QRemoteFileBrowser + QRemoteCopyWidget + QMachineBrowser + ) + +SET(qremotefilebrowser_LIBRARIES + "Qt5::Core;Qt5::Widgets" + ) + +SET(_moc_HEADERS + QRemoteFileBrowser + QMachineBrowser + QRemoteCopyWidget + ) + +# sources / moc wrappings +QT_WRAP_MOC(_moc_SOURCES ${_moc_HEADERS}) + +ADD_LIBRARY(qremotefilebrowser ${qremotefilebrowser_SOURCES} ${_moc_SOURCES}) +TARGET_LINK_LIBRARIES(qremotefilebrowser ${qremotefilebrowser_LIBRARIES}) +ADD_EXECUTABLE(remotefilebrowser remotefilebrowser.cxx) +TARGET_LINK_LIBRARIES(remotefilebrowser qremotefilebrowser) +INSTALL(TARGETS remotefilebrowser DESTINATION ${REMOTEFILEBROWSER_INSTALL_BINS}) +INSTALL(TARGETS qremotefilebrowser DESTINATION ${REMOTEFILEBROWSER_INSTALL_LIBS}) +INSTALL(FILES ${_moc_HEADERS} DESTINATION ${REMOTEFILEBROWSER_INSTALL_HEADERS}) diff --git a/tools/RemoteFileBrowser/QMachineBrowser b/tools/RemoteFileBrowser/QMachineBrowser new file mode 100644 index 000000000..f5c7f8520 --- /dev/null +++ b/tools/RemoteFileBrowser/QMachineBrowser @@ -0,0 +1,77 @@ +// Copyright (C) 2017 CEA/DEN, EDF R&D +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony GEAY (EDF R&D) + +#ifndef __QMACHINEBROWSER__ +#define __QMACHINEBROWSER__ + +#include +#include + +class QPushButton; +class QLineEdit; + +class QMachineSelector : public QComboBox +{ + Q_OBJECT +public: + QMachineSelector(QWidget *parent); + void initLocation(); +public slots: + void appendEntry(const QString& entry); +private: + void fillMachines(); + void fillMachinesFromCatalog(); + void fillMachinesFromSettings(); + void assignToLocalhost(); +}; + +class QMachineManager : public QWidget +{ + Q_OBJECT +public: + QMachineManager(QWidget *parent); + void initLocation(); + QString getSelectedHost() const; +public slots: + void newEntryRequested(); +private: + QPushButton *_pb; + QMachineSelector *_ms; +}; + +class QRemoteFileSystemModel; +class FileLoader; + +class QMachineBrowser : public QWidget +{ + Q_OBJECT +public: + QMachineBrowser(QWidget *parent=NULL); + void initLocation(); + QRemoteFileSystemModel *generateModel(); + FileLoader *generateFileLoader(); +signals: + void locationChanged(); +private: + QMachineManager *_msel; + QLineEdit *_le; +}; + +#endif diff --git a/tools/RemoteFileBrowser/QMachineBrowser.cxx b/tools/RemoteFileBrowser/QMachineBrowser.cxx new file mode 100644 index 000000000..9f5a65a8e --- /dev/null +++ b/tools/RemoteFileBrowser/QMachineBrowser.cxx @@ -0,0 +1,210 @@ +// Copyright (C) 2017 CEA/DEN, EDF R&D +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony GEAY (EDF R&D) + +#include "QMachineBrowser" +#include "QRemoteFileBrowser" + +#include "QDir" +#include "QFileInfo" +#include "QVBoxLayout" +#include "QPushButton" +#include "QMessageBox" +#include "QInputDialog" +#include "QXmlStreamReader" +#include "QProcessEnvironment" + +#include + +constexpr const char localhost[]="localhost"; + +QMachineSelector::QMachineSelector(QWidget *parent):QComboBox(parent) +{ + this->fillMachines(); +} + +void QMachineSelector::initLocation() +{ + this->assignToLocalhost(); +} + +void QMachineSelector::fillMachines() +{ + this->fillMachinesFromCatalog(); + this->fillMachinesFromSettings(); +} + +void QMachineSelector::appendEntry(const QString& entry) +{ + for(int i=0;icount();i++) + { + if(this->itemText(i)==entry) + return ; + } + this->insertItem(this->count(),entry); + this->setCurrentIndex(this->count()-1); +} + +void QMachineSelector::fillMachinesFromCatalog() +{ + constexpr const char APPLI[]="APPLI"; + constexpr const char RESOURCES[]="CatalogResources.xml"; + if(!QProcessEnvironment::systemEnvironment().contains(APPLI)) + return ; + QString appli(QProcessEnvironment::systemEnvironment().value(APPLI)); + QFileInfo fi(QDir::homePath(),appli); + if(!(fi.exists() && fi.isDir())) + return ; + QFileInfo fi2(QDir(fi.canonicalFilePath()),QString(RESOURCES)); + if(!(fi2.exists() && fi2.isFile())) + return ; + QFile file(fi2.canonicalFilePath()); + if(!file.open(QFile::ReadOnly | QFile::Text)) + { + return ; + } + QXmlStreamReader reader; + reader.setDevice(&file); + reader.readNext(); + while(!reader.atEnd()) + { + if(reader.isStartElement()) + { + if(reader.name()=="machine") + { + foreach(const QXmlStreamAttribute &attr, reader.attributes()) + { + if(attr.name().toString()==QLatin1String("name")) + { + this->insertItem(this->count(),attr.value().toString()); + } + } + } + reader.readNext(); + } + else + reader.readNext(); + } +} + +void QMachineSelector::assignToLocalhost() +{ + int i(0); + for(;icount();i++) + if(this->itemText(i)==localhost) + break ; + if(i==this->count()) + { + this->insertItem(this->count(),QString(localhost)); + this->setCurrentIndex(this->count()-1); + } + else + this->setCurrentIndex(i); +} + +void QMachineSelector::fillMachinesFromSettings() +{ +} + +QMachineManager::QMachineManager(QWidget *parent):QWidget(parent),_pb(nullptr),_ms(nullptr) +{ + QHBoxLayout *lay(new QHBoxLayout(this)); + _pb=new QPushButton(this); + _pb->setText("Add"); + _pb->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed); + lay->addWidget(_pb); + _ms=new QMachineSelector(this); + lay->addWidget(_ms); + connect(_pb,SIGNAL(clicked()),this,SLOT(newEntryRequested())); +} + +void QMachineManager::initLocation() +{ + _ms->initLocation(); +} + +QString QMachineManager::getSelectedHost() const +{ + return _ms->currentText(); +} + +void QMachineManager::newEntryRequested() +{ + constexpr int timeEllapse=3000; + bool isOK(false); + QString newEntry(QInputDialog::getItem(this,"Append new host","Hostname",QStringList(),/*current*/0,/*editable*/true,&isOK,Qt::Tool)); + if(!isOK) + return ; + { + QProcess proc; + { + QStringList st(newEntry); + st << "-c" << "1" << "-w" << QString::number(timeEllapse/1000);//attempt to send one packet within timeEllapse ms + proc.start("ping",st); + } + if(proc.waitForFinished(-1)) + { + if(proc.exitCode()!=0) + { + QMessageBox::information(this,"Information",QString("host %1 ping failed !").arg(newEntry)); + return ; + } + } + else + { + QMessageBox::information(this,"Information",QString("host %1 ping failed !").arg(newEntry)); + return ; + } + } + _ms->appendEntry(newEntry); +} + +QMachineBrowser::QMachineBrowser(QWidget *parent):QWidget(parent),_msel(nullptr),_le(nullptr) +{ + QVBoxLayout *lay(new QVBoxLayout(this)); + _msel=new QMachineManager(this); + _le=new QLineEdit(this); + lay->addWidget(_msel); + lay->addWidget(_le); + connect(_le,SIGNAL(returnPressed()),this,SIGNAL(locationChanged())); +} + +void QMachineBrowser::initLocation() +{ + _msel->initLocation(); + _le->setText(QDir::currentPath()); + emit this->locationChanged(); +} + +QRemoteFileSystemModel *QMachineBrowser::generateModel() +{ + FileLoader *fl(this->generateFileLoader()); + return new QRemoteFileSystemModel(this,fl); +} + +FileLoader *QMachineBrowser::generateFileLoader() +{ + FileLoader *fl(nullptr); + QString host(_msel->getSelectedHost()); + if(host==localhost) + fl=new LocalFileLoader(_le->text()); + else + fl=new RemoteFileLoader(host,_le->text()); + return fl; +} diff --git a/tools/RemoteFileBrowser/QRemoteCopyWidget b/tools/RemoteFileBrowser/QRemoteCopyWidget new file mode 100644 index 000000000..4814b0a67 --- /dev/null +++ b/tools/RemoteFileBrowser/QRemoteCopyWidget @@ -0,0 +1,128 @@ +// Copyright (C) 2017 CEA/DEN, EDF R&D +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony GEAY (EDF R&D) + +#ifndef __QREMOTECOPYWIDGET__ +#define __QREMOTECOPYWIDGET__ + +#include "QDialog" +#include "QMutex" +#include "QThread" +#include "QTreeView" +#include "QTableView" +#include "QItemDelegate" +#include "QPointer" +#include "QProcess" + +class QRemoteFileSystemModel; +class QFilesDirsCopierModel; +class DataStructure; +class QTableView; + +void PerformCopy(QWidget *parent, QRemoteFileSystemModel *srcModel, const QModelIndexList& srcSelectedFiles, DataStructure *ds); + +class CopierThread : public QThread +{ +public: + CopierThread(QObject *parent, QFilesDirsCopierModel *model):QThread(parent),_model(model) { } + void stopRequested(); +protected: + void run(); +private: + QFilesDirsCopierModel *_model; +}; + +class QFilesDirsCopierModel : public QAbstractListModel +{ + Q_OBJECT +public: + QFilesDirsCopierModel(QObject *parent, const QList& srcFiles, DataStructure *destLoc); + int nbOfRows() const; + int rowCount(const QModelIndex&) const; + int columnCount(const QModelIndex&) const; + QVariant data(const QModelIndex&, int) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + const QString& getErrorStr() const { return _error; } + int getProgressOf(int srcFileId) const; + QString getFullNameOf(int srcFileId) const; + QString getNameOf(int srcFileId) const; + QString getPrettyText(int srcFileId) const; + QSize sizeHint() const; + // + void launchCopy(); + void stopCurrentCopy(); +public slots: + void newOutputAvailable(); +private: + void fillArgsForRSync(const DataStructure *srcFile, QStringList& args) const; +private: + QList _srcFiles; + // + mutable QMutex _mutOnProc; + volatile int _currentElt; + QPointer _curProc; + QString _error; + // + QVector _progress; + DataStructure *_destLoc; +public: + static constexpr int PROGRESS_STATUS_START=-1; + static constexpr int PROGRESS_STATUS_OVER=101; + static const char ATOMIC_STOP_MSG[]; +}; + +class ProgressDelegate : public QItemDelegate +{ +public: + ProgressDelegate(QObject *parent, QFilesDirsCopierModel *model):QItemDelegate(parent),_model(model) { } + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; +private: + QFilesDirsCopierModel *_model; +}; + +class CopierTableView : public QTableView +{ +public: + CopierTableView(QWidget *parent); + int sizeHintForColumn(int column) const; + int sizeHintForRow(int row) const; + void resizeEvent(QResizeEvent *event); + QSize sizeHint() const; +}; + +class FilesDirsCopier : public QDialog +{ + Q_OBJECT +public: + FilesDirsCopier(QWidget *parent, const QList& srcFiles, DataStructure *destLoc); +public slots: + void cancelRequested(); + void myAccept(bool); + int exec(); + const QString& getErrorStr() const { return _model->getErrorStr(); } +signals: + void myAcceptSignal(bool); +private: + CopierTableView *_table; + QPushButton *_cancel; + CopierThread *_th; + QFilesDirsCopierModel *_model; +}; + +#endif diff --git a/tools/RemoteFileBrowser/QRemoteCopyWidget.cxx b/tools/RemoteFileBrowser/QRemoteCopyWidget.cxx new file mode 100644 index 000000000..57b2137d1 --- /dev/null +++ b/tools/RemoteFileBrowser/QRemoteCopyWidget.cxx @@ -0,0 +1,402 @@ +// Copyright (C) 2017 CEA/DEN, EDF R&D +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony GEAY (EDF R&D) + +#include "QRemoteCopyWidget" +#include "QRemoteFileBrowser" +#include "QVBoxLayout" +#include "QPushButton" +#include "QHeaderView" +#include "QResizeEvent" +#include "QProcess" +#include "QPainter" +#include "QPaintDevice" +#include "QMetaType" +#include "QApplication" +#include "QMessageBox" + +#include + +const char QFilesDirsCopierModel::ATOMIC_STOP_MSG[]="Clean interruption"; + +void FilterEntries(const TopDirDataStructure *tpds, QList& listToFilter) +{ + for(QList::iterator it(listToFilter.begin());it!=listToFilter.end();it++) + { + const DataStructure *elt(*it); + std::vector toKill(elt->getItermediateElts(tpds)); + for(QList::iterator it2(listToFilter.begin());it2!=listToFilter.end();) + { + if(it==it2) + { + it2++; + continue; + } + if(std::find(toKill.begin(),toKill.end(),*it2)!=toKill.end()) + it2=listToFilter.erase(it2); + else + it2++; + } + } +} + +void PerformCopy(QWidget *parent, QRemoteFileSystemModel *srcModel, const QModelIndexList& srcSelectedFiles, DataStructure *ds) +{ + QStringList sl; + if(srcSelectedFiles.isEmpty()) + return ; + // Filtering + const TopDirDataStructure *tpdsSrc(nullptr); + QList listOfEntries; + foreach(const QModelIndex& srcItem, srcSelectedFiles) + { + quintptr pt(srcItem.internalId()); + const DataStructure *srcDS(reinterpret_cast(pt)); + if(!srcDS) + continue; + if(!tpdsSrc) + tpdsSrc=srcDS->getRoot(); + listOfEntries.push_back(srcDS); + } + FilterEntries(tpdsSrc,listOfEntries); + // End filtering + FilesDirsCopier fdc(parent,listOfEntries,ds); + int res(fdc.exec()); + if(res!=QDialog::Accepted) + { + QMessageBox mb(QMessageBox::Warning,"Copy status",fdc.getErrorStr()); + mb.exec(); + } + +} + +void CopierThread::run() +{ + _model->launchCopy(); + FilesDirsCopier *par(qobject_cast(parent())); + if(!par) + return ; + emit par->myAcceptSignal(_model->getErrorStr().isEmpty());//emit signal to notify main thread. Not direct invocation because executed in different thread +} + +void CopierThread::stopRequested() +{ + _model->stopCurrentCopy(); + requestInterruption(); +} + +QFilesDirsCopierModel::QFilesDirsCopierModel(QObject *parent2, const QList& srcFiles, DataStructure *destLoc):QAbstractListModel(parent2),_srcFiles(srcFiles),_currentElt(0),_curProc(nullptr),_destLoc(destLoc),_progress(_srcFiles.size(),0) +{ + QTableView *par(qobject_cast(parent())); + par->resizeColumnToContents(0); + qRegisterMetaType>();//to be able to send 3th arg on dataChanged signal +} + +int QFilesDirsCopierModel::nbOfRows() const +{ + return _srcFiles.size(); +} + +int QFilesDirsCopierModel::rowCount(const QModelIndex&) const +{ + return nbOfRows(); +} + +int QFilesDirsCopierModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +QVariant QFilesDirsCopierModel::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + return QVariant(); + if(role==Qt::DisplayRole || role==Qt::EditRole) + { + return _srcFiles[index.row()]->fullName(); + } + return QVariant(); +} + +QVariant QFilesDirsCopierModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(role==Qt::DisplayRole || role==Qt::EditRole) + { + if(orientation==Qt::Horizontal) + { + return QString("Files to be copied"); + } + } + return QAbstractListModel::headerData(section,orientation,role); +} + +int QFilesDirsCopierModel::getProgressOf(int srcFileId) const +{ + QMutexLocker locker(&_mutOnProc); + return _progress[srcFileId]; +} + +QString QFilesDirsCopierModel::getFullNameOf(int srcFileId) const +{ + return _srcFiles[srcFileId]->fullName(); +} + +QString QFilesDirsCopierModel::getNameOf(int srcFileId) const +{ + return _srcFiles[srcFileId]->name(); +} + +QString QFilesDirsCopierModel::getPrettyText(int srcFileId) const +{ + int progress(getProgressOf(srcFileId)); + QString name(getNameOf(srcFileId)); + switch(progress) + { + case PROGRESS_STATUS_START: + return QString("Copy of %1 has started").arg(name); + case PROGRESS_STATUS_OVER: + return QString("Copy of %1 has finished").arg(name); + default: + return QString("%1 (%2%)").arg(name).arg(progress); + } +} + +QSize QFilesDirsCopierModel::sizeHint() const +{ + int w(0),h(0); + for(int zePos=0;zePos<_srcFiles.size();zePos++) + { + int progress(getProgressOf(zePos)); + QString txt(getPrettyText(zePos)); + QFont ft; + QFontMetrics fm(ft); + QSize sz(fm.boundingRect(txt).size()); + w=std::max(sz.width(),w); + h+=sz.height(); + } + return QSize(2*w,2*h); +} + +void QFilesDirsCopierModel::launchCopy() +{ + foreach(const DataStructure *srcFile,_srcFiles) + { + { + QMutexLocker locker(&_mutOnProc); + _progress[_currentElt]=PROGRESS_STATUS_START; + QModelIndex ind(this->index(_currentElt,0)); + emit this->dataChanged(ind,ind,QVector()); + } + if(QThread::currentThread()->isInterruptionRequested()) + { + _error=QString("%1 just before %2 (%3/%4)").arg(ATOMIC_STOP_MSG).arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size()); + return ; + } + { + QMutexLocker locker(&_mutOnProc); + _curProc=new QProcess; + QStringList args; + args << "--progress" << "-r"; + fillArgsForRSync(srcFile,args); + _curProc->start("rsync",args); + connect(_curProc,SIGNAL(readyReadStandardOutput()),this,SLOT(newOutputAvailable())); + } + bool s1(_curProc->waitForFinished(-1)); + bool s2(_curProc->exitStatus()==QProcess::NormalExit && _curProc->exitCode()==0); + if(s1 && s2) + { + QMutexLocker locker(&_mutOnProc); + _progress[_currentElt]=PROGRESS_STATUS_OVER; + QModelIndex ind(this->index(_currentElt,0)); + emit this->dataChanged(ind,ind,QVector()); + } + else + { + QMutexLocker locker(&_mutOnProc); + _error=QString("The copy of %1 has not finished correctly (%2/%3)").arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size()); + return ; + } + if(QThread::currentThread()->isInterruptionRequested()) + { + QMutexLocker locker(&_mutOnProc); + if(s1 && s2) + _error=QString("%1 right after %2 (%3/%4)").arg(ATOMIC_STOP_MSG).arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size()); + else + _error=QString("Interrupted during copy of %1 (%2/%3)").arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size()); + return ; + } + { + QMutexLocker locker(&_mutOnProc); + _curProc.clear(); + _currentElt++; + } + } +} + +void QFilesDirsCopierModel::stopCurrentCopy() +{ + QMutexLocker locker(&_mutOnProc); + if(!_curProc.isNull()) + _curProc->kill(); +} + +void QFilesDirsCopierModel::newOutputAvailable() +{ + QMutexLocker locker(&_mutOnProc); + QByteArray ba(_curProc->readAllStandardOutput()); + QString str(QString::fromLocal8Bit(ba)); + QRegularExpression re("[\\s]*([\\d\\,]+)[\\s]+([\\d]+)\\%[\\s]+([\\d]+\\.[\\d]{2}[Mk]B/s)[\\s]+([\\d]{1,2}:[\\d]{2}:[\\d]{2})"); + QRegularExpressionMatch rem(re.match(str,0,QRegularExpression::PartialPreferFirstMatch)); + if(rem.isValid()) + { + QString s(rem.captured(2)); + bool isOK(false); + int prog(s.toInt(&isOK)); + if(isOK) + { + _progress[_currentElt]=prog; + QModelIndex ind(this->index(_currentElt,0)); + emit this->dataChanged(ind,ind,QVector()); + } + } +} + +void QFilesDirsCopierModel::fillArgsForRSync(const DataStructure *srcFile, QStringList& args) const +{ + QString src(srcFile->entryForRSyncSrc()),dest(_destLoc->entryForRSyncDest()); + args << src << dest; +} + +void ProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + int zePos(index.row()); + QWidget *wid( dynamic_cast(painter->device()) ); + int progress(_model->getProgressOf(zePos)); + QString txt(_model->getPrettyText(zePos)); + QRect refRect(option.rect); + QFont ft(wid->font()); + QFontMetrics fm(ft); + QSize refRect2(fm.boundingRect(txt).size()); + if(progress==QFilesDirsCopierModel::PROGRESS_STATUS_OVER) + { + QFont ft2(ft); + ft.setBold(true); + painter->setFont(ft); + QPen p(painter->pen()); + painter->setPen(QPen(Qt::green)); + painter->drawText(QPoint(refRect.x()+(refRect.width()-refRect2.width())/2, + refRect.y()+refRect.height()/2+refRect2.height()/2-fm.descent()),txt); + painter->setPen(p); + painter->setFont(ft2); + return ; + } + if(progress==QFilesDirsCopierModel::PROGRESS_STATUS_START) + { + QFont ft2(ft); + ft.setBold(true); + painter->setFont(ft); + QPen p(painter->pen()); + painter->setPen(QPen(Qt::red)); + QString txt2(QString("Copy in progress of %1").arg(txt)); + painter->drawText(QPoint(refRect.x()+(refRect.width()-refRect2.width())/2, + refRect.y()+refRect.height()/2+refRect2.height()/2-fm.descent()),txt); + painter->setPen(p); + painter->setFont(ft2); + return ; + } + { + QBrush brush(Qt::green),b2(painter->brush()); + painter->setBrush(brush); + painter->drawRect(refRect.x(),refRect.y(),int((float)refRect.width()*float(progress/100.f)),refRect.height()); + painter->drawText(QPoint(refRect.x()+(refRect.width()-refRect2.width())/2, + refRect.y()+refRect.height()/2+refRect2.height()/2-fm.descent()),txt); + painter->setBrush(b2); + } +} + +CopierTableView::CopierTableView(QWidget *parent):QTableView(parent) +{ + setSelectionMode(QAbstractItemView::NoSelection); + setFocusPolicy(Qt::NoFocus); +} + +int CopierTableView::sizeHintForColumn(int column) const +{ + return width(); +} + +int CopierTableView::sizeHintForRow(int row) const +{ + QFilesDirsCopierModel *mod(qobject_cast(model())); + int nbElts(mod->nbOfRows()); + int sz((height()-horizontalHeader()->height())/(nbElts>0?nbElts:1)); + return sz; +} + +void CopierTableView::resizeEvent(QResizeEvent *event) +{ + resizeColumnToContents(0); + resizeRowsToContents(); + QTableView::resizeEvent(event); +} + +QSize CopierTableView::sizeHint() const +{ + QFilesDirsCopierModel *model2(qobject_cast(model())); + if(model2) + return model2->sizeHint(); + return CopierTableView::sizeHint(); +} + +FilesDirsCopier::FilesDirsCopier(QWidget *parent, const QList& srcFiles, DataStructure *destLoc):QDialog(parent),_table(new CopierTableView(this)),_cancel(new QPushButton(this)),_model(nullptr) +{ + QVBoxLayout *vb(new QVBoxLayout(this)); + _cancel->setText("Cancel"); + vb->addWidget(_table); + vb->addWidget(_cancel); + _model=new QFilesDirsCopierModel(_table,srcFiles,destLoc); + _th=new CopierThread(this,_model); + _table->setModel(_model); + _table->setShowGrid(false); + _table->setItemDelegate(new ProgressDelegate(_table,_model)); + connect(_cancel,SIGNAL(clicked()),this,SLOT(cancelRequested())); + connect(this,SIGNAL(myAcceptSignal(bool)),this,SLOT(myAccept(bool))); +} + +void FilesDirsCopier::cancelRequested() +{ + _th->stopRequested(); + _th->wait(); + reject(); +} + +void FilesDirsCopier::myAccept(bool isOK) +{ + _th->wait(); + if(isOK) + accept(); + else + reject(); +} + +int FilesDirsCopier::exec() +{ + _th->start(); + return QDialog::exec(); +} diff --git a/tools/RemoteFileBrowser/QRemoteFileBrowser b/tools/RemoteFileBrowser/QRemoteFileBrowser new file mode 100644 index 000000000..6474a3342 --- /dev/null +++ b/tools/RemoteFileBrowser/QRemoteFileBrowser @@ -0,0 +1,302 @@ +// Copyright (C) 2017 CEA/DEN, EDF R&D +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony GEAY (EDF R&D) + +#ifndef __QREMOTEFILEBROWSER__ +#define __QREMOTEFILEBROWSER__ + +#include "QWidget" +#include "QTreeView" +#include "QMimeData" +#include "QThread" + +class AnotherTreeView; +class QMachineBrowser; +class QRemoteFileSystemModel; +class TopDirDataStructure; + +class LoadingThread : public QThread +{ + Q_OBJECT +public: + LoadingThread(QThread *th, QMachineBrowser *mb):_fatherThread(th),_mb(mb),_model(nullptr) { } + void setGeneratedModel(QRemoteFileSystemModel *model) { _model=model; } + QRemoteFileSystemModel *generatedModel() const { return _model; } +signals: + void letsGenerateModel(TopDirDataStructure *fds); +protected: + void run(); +private: + QThread *_fatherThread; + QMachineBrowser *_mb; + QRemoteFileSystemModel *_model; +}; + +class QRemoteFileBrowser : public QWidget +{ + Q_OBJECT +public: + QRemoteFileBrowser(QWidget *parent); + QMachineBrowser *machineBrower() const { return _mb; } +public slots: + void onLocationChanged(); + void locationHasBeenChanged(); +private: + AnotherTreeView *_treeView; + QMachineBrowser *_mb; +}; + +class QRemoteFileTransfer : public QWidget +{ +public: + QRemoteFileTransfer(QWidget *parent=0); +private: + QRemoteFileBrowser *_left; + QRemoteFileBrowser *_right; +}; + +class DirDataStructure; +class TopDirDataStructure; + +class DataStructure : public QObject +{ + Q_OBJECT +public: + DataStructure(QObject *parent, const QString& name):QObject(parent),_name(name),_selected(false) { } + void select() { _selected=true; } + void unselect() { _selected=false; } + bool isSelected() const { return _selected; } + virtual bool isFile() const = 0; + virtual QString nameOnDrop() const = 0; + const DirDataStructure *getDirParent() const; + bool isRoot() const { return getDirParent()==NULL; } + const TopDirDataStructure *getRoot() const; + std::vector getItermediateElts(const TopDirDataStructure *tpds) const; + virtual int size() const = 0; + QString entryForRSyncSrc() const; + virtual QString entryForRSyncDest() const = 0; + QString name() const; + const QString& fullName() const { return _name; } + void removeFileArgs(QString& prg, QStringList& args) const; +private: + QString _name; + bool _selected; +}; + +class FileDataStructure : public DataStructure +{ +public: + FileDataStructure(DirDataStructure *dds, const QString& name); + bool isFile() const { return true; } + int size() const { return 0; } + QString entryForRSyncDest() const; + QString nameOnDrop() const; +}; + +class DirDataStructure : public DataStructure +{ +public: + DirDataStructure(DirDataStructure *dds, const QString& name):DataStructure(dds,name),_is_loaded(false),_is_expanded(false) { } + DirDataStructure(QObject *dds, const QString& name):DataStructure(dds,name),_is_loaded(false) { } + bool isFile() const { return false; } + int size() const { load(); return children().size(); } + QString entryForRSyncDest() const; + QString nameOnDrop() const { return name(); } + const DataStructure *operator[](int pos) const; + int posOf(const DataStructure *ds) const; + bool load() const; + void markAsLoaded() const { _is_loaded=true; } + void setExpanded(bool status) { _is_expanded=status; } + bool isExpanded() const { return _is_expanded; } +private: + mutable bool _is_loaded; + mutable bool _is_expanded; +}; + +class FileLoader; + +class TopDirDataStructure : public DirDataStructure +{ +public: + TopDirDataStructure(QObject *dds, FileLoader *fl); + virtual ~TopDirDataStructure(); + FileLoader *getLoader() const { return _fl; } + bool isOK() const { return _isOK; } + QString entryForRSync(const QString& fn) const; + QString getMachine() const; + void removeFileArgsImpl(const QString& filePath, QString& prg, QStringList& args) const; +private: + FileLoader *_fl; + bool _isOK; +}; + +class QRemoteFileSystemModel; + +class MyTreeView : public QTreeView +{ + Q_OBJECT +public: + MyTreeView(QWidget *parent); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dropEvent(QDropEvent *event); + QRemoteFileSystemModel *zeModel(); + void emitResetModel(); +public slots: + void itemExpanded(const QModelIndex &index); + void itemCollapsed(const QModelIndex &index); +signals: + void somethingChangedDueToFileModif(); +private: + void itemExpandedStatus(const QModelIndex &index, bool status); + void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const; + void paintEvent(QPaintEvent *event); +private: + QPoint _start_pos; + // during drag drop _sel is the element under the mouse on the drop site + DataStructure *_sel; + // during drag drop _slider_pos is the pos of vertical slider on drop site + int _slider_pos; +}; + +class AnotherTreeView; + +class AnotherTreeViewPainter +{ +public: + virtual void paint(AnotherTreeView *atv, QPaintEvent *event) const = 0; +}; + +class AnotherTreeViewWaitPainter : public AnotherTreeViewPainter +{ +public: + void paint(AnotherTreeView *atv, QPaintEvent *event) const; +}; + +class AnotherTreeViewNothingPainter : public AnotherTreeViewPainter +{ +public: + void paint(AnotherTreeView *atv, QPaintEvent *event) const; +}; + +class AnotherTreeView : public QWidget +{ + Q_OBJECT +public: + AnotherTreeView(QWidget *parent); + void generateModel(QMachineBrowser *mb); + QSize sizeHint() const; + QSize minimumSizeHint() const; + int getAngle() const { return _angle; } + ~AnotherTreeView(); +public slots: + void goGenerate(TopDirDataStructure *fds); + void modelHasBeenGenerated(); +signals: + void modelHasBeenGeneratedSignal(bool isOK); + void somethingChangedDueToFileModif(); +protected: + void paintEvent(QPaintEvent *event); + void timerEvent(QTimerEvent *e); +private: + int _timerId; + int _angle; + AnotherTreeViewPainter *_painter; + MyTreeView *_tw; + LoadingThread *_th; +}; + +class FileLoader +{ +protected: + FileLoader(const QString& dirName):_dirName(dirName) { } +public: + QString getDirName() const { return _dirName; } + virtual bool load(DirDataStructure *parent) const = 0; + virtual QString prettyPrint() const = 0; + virtual QString entryForRSync(const QString& fn) const = 0; + virtual QString getMachine() const = 0; + virtual void removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const = 0; + virtual ~FileLoader() { } +private: + QString _dirName; +}; + +class LocalFileLoader : public FileLoader +{ +public: + LocalFileLoader(const QString& dirName):FileLoader(dirName) { } + bool load(DirDataStructure *parent) const; + void fillArgs(const QString& dn, QString& prg, QStringList& args) const; + QString prettyPrint() const; + QString entryForRSync(const QString& fn) const; + QString getMachine() const; + void removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const; +}; + +class RemoteFileLoader : public FileLoader +{ +public: + RemoteFileLoader(const QString& machine,const QString& dirName):FileLoader(dirName),_machine(machine) { } + bool load(DirDataStructure *parent) const; + void fillArgs(const QString& dn, QString& prg, QStringList& args) const; + QString prettyPrint() const; + QString entryForRSync(const QString& fn) const; + QString getMachine() const; + void removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const; +private: + QString _machine; +}; + +class SelectionMimeData : public QMimeData +{ + Q_OBJECT +public: + SelectionMimeData(const QModelIndexList& los):_los(los) { } + const QModelIndexList& getSelection() const { return _los; } +private: + QModelIndexList _los; +}; + +class QRemoteFileSystemModel : public QAbstractItemModel +{ + Q_OBJECT +public: + QRemoteFileSystemModel(QObject *parent, FileLoader *fl); + QRemoteFileSystemModel(QObject *parent, TopDirDataStructure *fds); + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex parent(const QModelIndex&) const; + QModelIndex index(int, int, const QModelIndex&) const; + int rowCount(const QModelIndex&) const; + int columnCount(const QModelIndex&) const; + QVariant data(const QModelIndex&, int) const; + void emitResetModel(); + FileLoader *getLoader() const { return _fds->getLoader(); } + bool isOK() const { return _fds->isOK(); } + QString getMachine() const { return _fds->getMachine(); } +private: + TopDirDataStructure *_fds; +}; + +#endif diff --git a/tools/RemoteFileBrowser/QRemoteFileBrowser.cxx b/tools/RemoteFileBrowser/QRemoteFileBrowser.cxx new file mode 100644 index 000000000..8d28a8eae --- /dev/null +++ b/tools/RemoteFileBrowser/QRemoteFileBrowser.cxx @@ -0,0 +1,892 @@ +// Copyright (C) 2017 CEA/DEN, EDF R&D +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony GEAY (EDF R&D) + +#include "QRemoteFileBrowser" +#include "QMachineBrowser" +#include "QRemoteCopyWidget" + +#include "QDirModel" +#include "QFileSystemModel" +#include "QVBoxLayout" +#include "QTreeView" +#include "QProcess" +#include "QMouseEvent" +#include "QApplication" +#include "QDrag" +#include "QScrollBar" +#include "QThread" +#include "QPainter" +#include "QMessageBox" + +#include + +class DirDataStructure; + +void ListOfDir(DirDataStructure *parent); + +QRemoteFileBrowser::QRemoteFileBrowser(QWidget *parent):QWidget(parent),_treeView(nullptr),_mb(nullptr) +{ + _treeView=new AnotherTreeView(this); + QVBoxLayout *lay(new QVBoxLayout(this)); + _mb=new QMachineBrowser(this); + lay->addWidget(_mb); + lay->addWidget(_treeView); + connect(_mb,SIGNAL(locationChanged()),this,SLOT(onLocationChanged())); + connect(_treeView,SIGNAL(modelHasBeenGeneratedSignal(bool)),this,SLOT(locationHasBeenChanged())); + connect(_treeView,SIGNAL(somethingChangedDueToFileModif()),this,SLOT(onLocationChanged())); + _mb->initLocation(); +} + +void LoadingThread::run() +{ + //std::cout << "start" << std::endl; + FileLoader *fl(_mb->generateFileLoader()); + TopDirDataStructure *fds(new TopDirDataStructure(nullptr,fl));//costly + fds->moveToThread(_fatherThread); + emit this->letsGenerateModel(fds); + //std::cout << "end" << std::endl; +} + +void QRemoteFileBrowser::onLocationChanged() +{ + _mb->setEnabled(false); + _treeView->generateModel(_mb); +} + +void QRemoteFileBrowser::locationHasBeenChanged() +{ + _mb->setEnabled(true); +} + +QRemoteFileTransfer::QRemoteFileTransfer(QWidget *parent):QWidget(parent),_left(nullptr),_right(nullptr) +{ + QHBoxLayout *lay(new QHBoxLayout(this)); + _left=new QRemoteFileBrowser(this); + _right=new QRemoteFileBrowser(this); + lay->addWidget(_left); + lay->addWidget(_right); +} + +QString DataStructure::name() const +{ + QDir qd(_name); + return qd.dirName(); +} + +void DataStructure::removeFileArgs(QString& prg, QStringList& args) const +{ + const TopDirDataStructure *root(getRoot()); + root->removeFileArgsImpl(fullName(),prg,args); +} + +QString DataStructure::entryForRSyncSrc() const +{ + const TopDirDataStructure *root(getRoot()); + return root->entryForRSync(fullName()); +} + +QString FileDataStructure::entryForRSyncDest() const +{ + const TopDirDataStructure *root(getRoot()); + return root->entryForRSync(getDirParent()->fullName()); +} + +QString DirDataStructure::entryForRSyncDest() const +{ + const TopDirDataStructure *root(getRoot()); + return root->entryForRSync(fullName()); +} + +class FileLoader; + +const TopDirDataStructure *DataStructure::getRoot() const +{ + if(isRoot()) + return dynamic_cast(this); + const DataStructure *work(this); + const DataStructure *ret(work->getDirParent()); + while(true) + { + if(ret->isRoot()) + return dynamic_cast(ret); + work=ret; + ret=ret->getDirParent(); + } +} + +std::vector DataStructure::getItermediateElts(const TopDirDataStructure *tpds) const +{ + std::vector ret; + if(isRoot()) + return ret; + const DataStructure *work(this); + const DataStructure *cand(work->getDirParent()); + while(true) + { + if(cand==tpds) + return ret; + work=cand; + ret.push_back(cand); + cand=cand->getDirParent(); + } +} + +void LocalFileLoader::fillArgs(const QString& dn, QString& prg, QStringList& args) const +{ + prg="ls"; args << "-l" << dn; +} + +QString LocalFileLoader::prettyPrint() const +{ + return QString("Browsing %1 local directory").arg(getDirName()); +} + +QString LocalFileLoader::entryForRSync(const QString& fn) const +{ + return fn; +} + +QString LocalFileLoader::getMachine() const +{ + return QString("localhost"); +} + +void LocalFileLoader::removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const +{ + prg="rm"; + args << "-rf" << filePath; +} + +void RemoteFileLoader::fillArgs(const QString& dn, QString& prg, QStringList& args) const +{ + // find non hidden file recursive. If dn does not exist propagate the error status + prg="ssh"; args << _machine << QString("find %1 -maxdepth 1 -regextype egrep -regex '%1/[^\\.].*' | xargs ls -l ; test ${PIPESTATUS[0]} -eq 0").arg(dn); +} + +QString RemoteFileLoader::prettyPrint() const +{ + return QString("Browsing files in %1 on %2").arg(getDirName()).arg(_machine); +} + +QString RemoteFileLoader::entryForRSync(const QString& fn) const +{ + return QString("%1:%2").arg(_machine).arg(fn); +} + +QString RemoteFileLoader::getMachine() const +{ + return _machine; +} + +void RemoteFileLoader::removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const +{ + prg="ssh"; + args << _machine << "rm" << "-rf" << filePath; +} + +bool LocalFileLoader::load(DirDataStructure *parent) const +{ + QProcess *proc(new QProcess); + QString prg; + QStringList args; + + fillArgs(parent->fullName(),prg,args); + + proc->start(prg,args); + static const int timeEllapse(3000); + if(!proc->waitForFinished(timeEllapse)) + return false; + if(proc->exitCode()!=0) + return false; + QByteArray arr(proc->readAll()); + delete proc; + QList sarr(arr.split('\n')); + std::size_t ii(0); + QRegularExpression re1("\\s+"); + QRegularExpression re2("^(d|-)(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)$"); + foreach(QByteArray arr0,sarr) + { + bool specialCase(ii==0 || ii==sarr.size()-1); + ii++; + if(specialCase) + continue; + QStringList arr1(QString(arr0).split(re1)); + if(arr1.size()<9) + continue; + QRegularExpressionMatch match(re2.match(arr1[0])); + if(!match.hasMatch()) + continue; + QDir qd(parent->fullName()); + if(match.captured(1)=="d") + new DirDataStructure(parent,qd.absoluteFilePath(arr1[8])); + if(match.captured(1)=="-") + new FileDataStructure(parent,qd.absoluteFilePath(arr1[8])); + } + return true; +} + +void readStdPart(DirDataStructure *parent, const QList& arrs, int startLine, int endLine) +{ + QRegularExpression re1("\\s+"); + QRegularExpression re2("^(d|-)(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)$"); + for(int i=startLine;ifullName()); + if(match.captured(1)=="d") + { + //std::cout << "Dir " << qd.absoluteFilePath(arr1[8]) << std::endl; + new DirDataStructure(parent,qd.absoluteFilePath(arr1[8])); + } + if(match.captured(1)=="-") + { + //std::cout << "File " << qd.absoluteFilePath(arr1[8]) << std::endl; + new FileDataStructure(parent,qd.absoluteFilePath(arr1[8])); + } + } + parent->markAsLoaded(); +} + +bool RemoteFileLoader::load(DirDataStructure *parent) const +{ + QProcess *proc(new QProcess); + QString prg; + QStringList args; + fillArgs(parent->fullName(),prg,args); + proc->start(prg,args); + static const int timeEllapse(3000); + if(!proc->waitForFinished(timeEllapse)) + return false; + if(proc->exitCode()!=0) + return false; + QByteArray arr(proc->readAll()); + delete proc; + QList sarr(arr.split('\n')); + if(sarr.empty()) + return false; + int sz(sarr.size()),ii(0); + std::vector sections(1,0); + foreach(QByteArray arr0,sarr) + { + QString tmp(arr0); + if(tmp.size()==0) + sections.push_back(ii); + ii++; + } + QRegularExpression re3("^total ([0-9]+)$"); + std::size_t nbSections(sections.size()-1); + for(std::size_t isec=0;isecfullName()).arg(QDir::separator())); + QRegularExpressionMatch m4(re4.match(almostDn)); + if(!m4.hasMatch()) + continue ; + QString dn(almostDn.mid(0,almostDn.size()-1)); + DirDataStructure *subParent(new DirDataStructure(parent,dn)); + QString total(sarr[startLine+2]); + QRegularExpressionMatch m3(re3.match(total)); + if(!m3.hasMatch()) + continue; + readStdPart(subParent,sarr,startLine+3,endLine); + } + // sort it ! + const QObjectList &cs(parent->children()); + std::map< QString, QObject *> sorter; + foreach(QObject *child,cs) + { + DataStructure *child2(qobject_cast(child)); + if(!child2) + continue ; + sorter[child2->name()]=child2; + child2->setParent(nullptr); + } + for(std::map< QString, QObject *>::const_iterator it=sorter.begin();it!=sorter.end();it++) + (*it).second->setParent(parent); + return true; +} + +bool DirDataStructure::load() const +{ + bool ret(true); + if(!_is_loaded) + { + ret=getRoot()->getLoader()->load(const_cast(this)); + _is_loaded=true; + } + return ret; +} + +TopDirDataStructure::TopDirDataStructure(QObject *dds, FileLoader *fl):DirDataStructure(dds,fl->getDirName()),_fl(fl),_isOK(true) +{ + _isOK=load(); + //QThread::sleep(3); +} + +TopDirDataStructure::~TopDirDataStructure() +{ + delete _fl; +} + +QString TopDirDataStructure::entryForRSync(const QString& fn) const +{ + return _fl->entryForRSync(fn); +} + +QString TopDirDataStructure::getMachine() const +{ + return _fl->getMachine(); +} + +void TopDirDataStructure::removeFileArgsImpl(const QString& filePath, QString& prg, QStringList& args) const +{ + _fl->removeFileArgs(filePath,prg,args); +} + +const DirDataStructure *DataStructure::getDirParent() const +{ + const QObject *p(parent()); + if(!p) + return NULL; + const DirDataStructure *ret(dynamic_cast(p)); + return ret; +} + +int DirDataStructure::posOf(const DataStructure *ds) const +{ + load(); + return children().indexOf(const_cast(ds)); +} + +const DataStructure *DirDataStructure::operator[](int pos) const +{ + load(); + const QObject *obj(children()[pos]); + const DataStructure *obj2(dynamic_cast(obj)); + return obj2; +} + +FileDataStructure::FileDataStructure(DirDataStructure *dds, const QString& name):DataStructure(dds,name) +{ +} + +QString FileDataStructure::nameOnDrop() const +{ + const DirDataStructure *ds3(getDirParent()); + return QString("Dir %1 (%2)").arg(ds3->name()).arg(name()); +} + +void QRemoteFileSystemModel::emitResetModel() +{ + emit this->beginResetModel(); + emit this->endResetModel(); +} + +QRemoteFileSystemModel::QRemoteFileSystemModel(QObject *parent, FileLoader *fl):QAbstractItemModel(parent),_fds(nullptr) +{ + _fds=new TopDirDataStructure(this,fl); +} + +QRemoteFileSystemModel::QRemoteFileSystemModel(QObject *parent, TopDirDataStructure *fds):QAbstractItemModel(parent),_fds(fds) +{ + if(_fds) + _fds->setParent(this); +} + +QVariant QRemoteFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(role == Qt::DisplayRole || role == Qt::EditRole) + { + if(section==0) + return _fds->getLoader()->prettyPrint(); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QModelIndex QRemoteFileSystemModel::parent(const QModelIndex& child) const +{ + if(!child.isValid()) + return QModelIndex(); + quintptr pt(child.internalId()); + DataStructure *ds(reinterpret_cast(pt)); + if(!ds) + return QModelIndex(); + if(ds->isRoot()) + return QModelIndex(); + const DirDataStructure *father(ds->getDirParent()); + if(father->isRoot()) + return QModelIndex(); + const DirDataStructure *grandFather(father->getDirParent()); + return createIndex(grandFather->posOf(father),0,const_cast(static_cast(father))); +} + +QModelIndex QRemoteFileSystemModel::index(int row, int column, const QModelIndex& parent) const +{ + quintptr pt(parent.internalId()); + if(!pt) + { + if(_fds->size()<=row) + return QModelIndex(); + const DataStructure *ds((*_fds)[row]); + return createIndex(row,column,const_cast(ds)); + } + else + { + DataStructure *ds0(reinterpret_cast(pt)); + DirDataStructure *ds1(dynamic_cast(ds0)); + const DataStructure *ds((*ds1)[row]); + return createIndex(row,column,const_cast(ds)); + } +} + +int QRemoteFileSystemModel::rowCount(const QModelIndex& mi) const +{ + if(!mi.isValid()) + return _fds->size(); + quintptr pt(mi.internalId()); + DataStructure *ds(reinterpret_cast(pt)); + if(!ds) + { + return _fds->size(); + } + else + { + return ds->size(); + } +} + +int QRemoteFileSystemModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +QVariant QRemoteFileSystemModel::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + return QVariant(); + if(role==Qt::DisplayRole || role==Qt::EditRole) + { + quintptr pt(index.internalId()); + DataStructure *ds(reinterpret_cast(pt)); + if(!ds->isSelected()) + return ds->name(); + return ds->nameOnDrop(); + } + if(role==Qt::ForegroundRole) + { + quintptr pt(index.internalId()); + DataStructure *ds(reinterpret_cast(pt)); + if(!ds->isSelected()) + return QVariant(); + QColor red0(154,18,20); + return QBrush(red0); + } + return QVariant(); +} + +MyTreeView::MyTreeView(QWidget *parent):QTreeView(parent),_sel(NULL),_slider_pos(-1) +{ + setAcceptDrops(true); + setSelectionMode(QAbstractItemView::ContiguousSelection); + connect(this,SIGNAL(expanded(const QModelIndex&)),this,SLOT(itemExpanded(const QModelIndex&))); + connect(this,SIGNAL(collapsed(const QModelIndex&)),this,SLOT(itemCollapsed(const QModelIndex&))); +} + +void MyTreeView::mousePressEvent(QMouseEvent *event) +{ + if(event->button() == Qt::LeftButton) + { + _start_pos=event->pos(); + } + QModelIndexList elts(selectedIndexes()); + if(elts.size()<=1) + QTreeView::mousePressEvent(event); +} + +void MyTreeView::mouseReleaseEvent(QMouseEvent *event) +{ + QModelIndexList elts(selectedIndexes()); + if(elts.size()>1) + QTreeView::mousePressEvent(event); + QTreeView::mouseReleaseEvent(event); +} + +void MyTreeView::mouseMoveEvent(QMouseEvent *event) +{ + if(event->buttons() & Qt::LeftButton) + { + int dist(std::abs(event->pos().x()-_start_pos.x()));//.manhattanLength()); + if(dist>=QApplication::startDragDistance()) + { + QMimeData *mimeData(new SelectionMimeData(selectedIndexes())); + QDrag *drag(new QDrag(this)); + drag->setMimeData(mimeData); + if(drag->exec(Qt::CopyAction)==Qt::CopyAction) + { + } + else + { + } + } + } + QTreeView::mouseMoveEvent(event); +} + +void MyTreeView::keyPressEvent(QKeyEvent *event) +{ + if(event->key()==Qt::Key_Delete) + { + if(!selectedIndexes().isEmpty()) + { + QString mach; + QRemoteFileSystemModel *mod(qobject_cast(model())); + if(mod) + mach=mod->getMachine(); + QString txt(QString("On %1 you are about to delete %2 files/dirs. Confirm it ?").arg(mach).arg(selectedIndexes().size())); + QMessageBox mb(QMessageBox::Warning,"Confirm deletion of files",txt,QMessageBox::Ok | QMessageBox::No,this); + mb.setEscapeButton(QMessageBox::No); + mb.setDefaultButton(QMessageBox::No); + if(mb.exec()) + { + if(mb.buttonRole(mb.clickedButton())==QMessageBox::AcceptRole) + { + foreach(const QModelIndex& ind,selectedIndexes()) + { + quintptr pt(ind.internalId()); + DataStructure *ds(reinterpret_cast(pt)); + QString prg; + QStringList args; + ds->removeFileArgs(prg,args); + QProcess proc; + proc.start(prg,args); + bool isOK(proc.waitForFinished()); + emit this->somethingChangedDueToFileModif(); + if(isOK && proc.exitCode()==0) + { + return ; + } + } + } + } + } + } + QTreeView::keyPressEvent(event); +} + +void MyTreeView::dragEnterEvent(QDragEnterEvent *event) +{ + MyTreeView *source(qobject_cast(event->source())); + if(source && source!=this) + { + _slider_pos=verticalScrollBar()->sliderPosition(); + event->setDropAction(Qt::CopyAction); + event->accept(); + } +} + +void MyTreeView::dragMoveEvent(QDragMoveEvent *event) +{ + MyTreeView *source(qobject_cast(event->source())); + if(source && source!=this) + { + QModelIndex ind(indexAt(event->pos())); + if(ind.isValid()) + { + quintptr pt(ind.internalId()); + DataStructure *ds(reinterpret_cast(pt)); + if(ds) + { + if(_sel!=ds) + { + ds->select(); + if(_sel) + _sel->unselect(); + _sel=ds; + this->emitResetModel(); + } + event->setDropAction(Qt::CopyAction); + event->accept(); + qApp->processEvents(); + } + } + } +} + +void MyTreeView::dragLeaveEvent(QDragLeaveEvent *event) +{ + if(_sel) + { + _sel->unselect(); + _sel=NULL; + } + _slider_pos=-1; + this->emitResetModel(); + QTreeView::dragLeaveEvent(event); +} + +void MyTreeView::dropEvent(QDropEvent *event) +{ + MyTreeView *source(qobject_cast(event->source())); + if(source && source!=this) + { + { + const QMimeData *data(event->mimeData()); + const SelectionMimeData *data1(qobject_cast(data)); + if(!data1) + { + _sel->unselect(); + event->ignore(); + this->emitResetModel(); + } + QModelIndex ind(indexAt(event->pos())); + if(ind.isValid()) + { + const QModelIndexList& listOfSelectedSrcFiles(data1->getSelection()); + quintptr pt(ind.internalId()); + DataStructure *ds(reinterpret_cast(pt)); + QRemoteFileSystemModel *srcModel(qobject_cast(source->model())); + PerformCopy(this,srcModel,listOfSelectedSrcFiles,ds); + emit this->somethingChangedDueToFileModif(); + } + else + { + event->ignore(); + this->emitResetModel(); + } + } + // + _slider_pos=-1; + event->setDropAction(Qt::MoveAction); + event->accept(); + if(_sel) + _sel->unselect(); + this->emitResetModel(); + } +} + +QRemoteFileSystemModel *MyTreeView::zeModel() +{ + QAbstractItemModel *mod(this->model()); + QRemoteFileSystemModel *mod2(qobject_cast(mod)); + return mod2; +} + +void MyTreeView::itemExpanded(const QModelIndex &index) +{ + itemExpandedStatus(index,true); +} + +void MyTreeView::itemCollapsed(const QModelIndex &index) +{ + itemExpandedStatus(index,false); +} + +void MyTreeView::itemExpandedStatus(const QModelIndex &index, bool status) +{ + quintptr pt(index.internalId()); + DataStructure *ds(reinterpret_cast(pt)); + DirDataStructure *ds1(dynamic_cast(ds)); + if(!ds1) + return; + ds1->setExpanded(status); +} + +void MyTreeView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const +{ + quintptr pt(index.internalId()); + DataStructure *ds(reinterpret_cast(pt)); + DirDataStructure *ds1(dynamic_cast(ds)); + if(ds1) + (const_cast(this))->setExpanded(index,ds1->isExpanded()); + QTreeView::drawBranches(painter,rect,index); +} + +void MyTreeView::paintEvent(QPaintEvent *event) +{ + QTreeView::paintEvent(event); + if(_slider_pos!=-1) + { + verticalScrollBar()->setSliderPosition(_slider_pos); + } +} + +void MyTreeView::emitResetModel() +{ + this->zeModel()->emitResetModel(); +} + +AnotherTreeView::AnotherTreeView(QWidget *parent):QWidget(parent),_timerId(-1),_angle(0),_painter(nullptr),_tw(nullptr),_th(nullptr) +{ + QVBoxLayout *lay(new QVBoxLayout(this)); + _tw=new MyTreeView(this); + lay->addWidget(_tw); + setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); + connect(_tw,SIGNAL(somethingChangedDueToFileModif()),this,SIGNAL(somethingChangedDueToFileModif())); + _tw->hide(); +} + +void AnotherTreeView::goGenerate(TopDirDataStructure *fds) +{ + LoadingThread *s2(qobject_cast(sender())); + if(!s2) + return ; + QRemoteFileSystemModel *model(new QRemoteFileSystemModel(_tw,fds)); + s2->setGeneratedModel(model); +} + +void AnotherTreeView::generateModel(QMachineBrowser *mb) +{ + _tw->hide(); + _th=new LoadingThread(QThread::currentThread(),mb); + connect(_th,SIGNAL(letsGenerateModel(TopDirDataStructure *)),this,SLOT(goGenerate(TopDirDataStructure *))); + connect(_th,SIGNAL(finished()),this,SLOT(modelHasBeenGenerated())); + _timerId=this->startTimer(50); + delete _painter; + _painter=new AnotherTreeViewWaitPainter; + _th->start(); +} + +AnotherTreeView::~AnotherTreeView() +{ + delete _painter; +} + +void AnotherTreeView::modelHasBeenGenerated() +{ + _th->wait(); + QRemoteFileSystemModel *model(_th->generatedModel()); + { + QAbstractItemModel *oldModel(_tw->model()); + if(oldModel) + delete oldModel; + _tw->setModel(model); + } + delete _th; + _th=nullptr; + this->killTimer(_timerId); + delete _painter; + _painter=nullptr; + if(!model->isOK()) + _painter=new AnotherTreeViewNothingPainter; + else + _tw->show(); + emit this->modelHasBeenGeneratedSignal(model->isOK()); + updateGeometry(); + update(); +} + +void AnotherTreeViewWaitPainter::paint(AnotherTreeView *atv, QPaintEvent *event) const +{ + QSize sz3(atv->width(),atv->height()); + int width0(sz3.width()),height0(sz3.height()); + int radius(std::min(width0,height0)/2); + QRect refRect((width0-radius)/2,(height0-radius)/2,radius,radius); + QPainter painter(atv); + QColor red(154,18,20); + QRadialGradient grad(refRect.center(),radius); + grad.setColorAt(0.f,red); + grad.setColorAt(0.5f,Qt::white); + painter.setBrush(grad); + painter.drawPie(refRect,atv->getAngle(),90*16); +} + +void AnotherTreeViewNothingPainter::paint(AnotherTreeView *atv, QPaintEvent *event) const +{ + QPainter painter(atv); + const int SZP(12); + static const int WARN_Y=176,WARN_X=200; + const float RATIO(float(WARN_X)/float(WARN_Y)); + // + int width0(atv->width()),height0(atv->height()); + //QPen(QColor(255,203,189) + if(float(width0)>RATIO*float(height0)) + painter.setViewport(int(width0-RATIO*float(height0))/2,0,height0*RATIO,height0); + else + painter.setViewport(0,(float(height0)-width0/RATIO)/2,width0,width0/RATIO);//width-height/RATIO + painter.setRenderHint(QPainter::Antialiasing,true); + painter.setWindow(0,0,WARN_X,WARN_Y); + // + painter.setPen(QPen(QColor(255,203,189),SZP,Qt::SolidLine,Qt::RoundCap)); + painter.drawLine(QPoint(100,13),QPoint(11,164)); + painter.drawLine(QPoint(11,164),QPoint(185,164)); + painter.drawLine(QPoint(185,164),QPoint(100,13)); + QColor lightBlack(200,200,200); + painter.setBrush(QBrush(lightBlack)); + painter.setPen(QPen(lightBlack,2,Qt::SolidLine,Qt::RoundCap)); + painter.drawEllipse(87,47,24,24); + painter.drawEllipse(93,105,12,12); + painter.drawEllipse(90,129,18,18); + const QPoint points[4]={QPoint(87,59),QPoint(93,111),QPoint(105,111),QPoint(111,59)}; + painter.drawPolygon(points,4); + + /*int width0(atv->width()),height0(atv->height()); + int radius(std::min(width0,height0)/2); + QRect refRect((width0-radius)/2,(height0-radius)/2,radius,radius); + QPainter painter(atv); + QColor red(154,18,20); + painter.setBrush(QBrush(red)); + painter.drawPie(refRect,0,45*16);*/ +} + +QSize AnotherTreeView::sizeHint() const +{ + return _tw->sizeHint(); +} + +QSize AnotherTreeView::minimumSizeHint() const +{ + return sizeHint(); +} + +void AnotherTreeView::paintEvent(QPaintEvent *event) +{ + if(!_painter) + { + QWidget::paintEvent(event); + return ; + } + else + _painter->paint(this,event); +} + +void AnotherTreeView::timerEvent(QTimerEvent *e) +{ + if(e->timerId()!=_timerId) + return ; + _angle+=16*10; + update(); +} diff --git a/tools/RemoteFileBrowser/remotefilebrowser.cxx b/tools/RemoteFileBrowser/remotefilebrowser.cxx new file mode 100644 index 000000000..af7c57416 --- /dev/null +++ b/tools/RemoteFileBrowser/remotefilebrowser.cxx @@ -0,0 +1,30 @@ +// Copyright (C) 2017 CEA/DEN, EDF R&D +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony GEAY (EDF R&D) + +#include "QApplication" +#include "QRemoteFileBrowser" + +int main(int argc, char *argv[]) +{ + QApplication app(argc,argv); + QRemoteFileTransfer ft; + ft.show(); + return app.exec(); +} -- 2.39.2