Salome HOME
Updated copyright comment
[modules/gui.git] / tools / RemoteFileBrowser / QRemoteCopyWidget.cxx
1 // Copyright (C) 2017-2024  CEA, EDF
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19 // Author : Anthony GEAY (EDF R&D)
20
21 #include "QRemoteCopyWidget.h"
22 #include "QRemoteFileBrowser.h"
23
24 #include "QVBoxLayout"
25 #include "QPushButton"
26 #include "QHeaderView"
27 #include "QResizeEvent"
28 #include "QProcess"
29 #include "QPainter"
30 #include "QPaintDevice"
31 #include "QMetaType"
32 #include "QApplication"
33 #include "QMessageBox"
34
35 #include <iostream>
36
37 const char QFilesDirsCopierModel::ATOMIC_STOP_MSG[]="Clean interruption";
38
39 void FilterEntries(const TopDirDataStructure *tpds, QList<const DataStructure *>& listToFilter)
40 {
41   for(QList<const DataStructure *>::iterator it(listToFilter.begin());it!=listToFilter.end();it++)
42     {
43       const DataStructure *elt(*it);
44       std::vector<const DataStructure *> toKill(elt->getItermediateElts(tpds));
45       for(QList<const DataStructure *>::iterator it2(listToFilter.begin());it2!=listToFilter.end();)
46         {
47           if(it==it2)
48             {
49               it2++;
50               continue;
51             }
52           if(std::find(toKill.begin(),toKill.end(),*it2)!=toKill.end())
53             it2=listToFilter.erase(it2);
54           else
55             it2++;
56         }
57     }
58 }
59
60 void PerformCopy(QWidget *parent, QRemoteFileSystemModel *srcModel, const QModelIndexList& srcSelectedFiles, DataStructure *ds)
61 {
62   QStringList sl;
63   if(srcSelectedFiles.isEmpty())
64     return ;
65   // Filtering
66   const TopDirDataStructure *tpdsSrc(nullptr);
67   QList<const DataStructure *> listOfEntries;
68   foreach(const QModelIndex& srcItem, srcSelectedFiles)
69     {
70       quintptr pt(srcItem.internalId());
71       const DataStructure *srcDS(reinterpret_cast<const DataStructure *>(pt));
72       if(!srcDS)
73         continue;
74       if(!tpdsSrc)
75         tpdsSrc=srcDS->getRoot();
76       listOfEntries.push_back(srcDS);
77     }
78   FilterEntries(tpdsSrc,listOfEntries);
79   // End filtering
80   FilesDirsCopier fdc(parent,listOfEntries,ds);
81   int res(fdc.exec());
82   if(res!=QDialog::Accepted)
83     {
84       QMessageBox mb(QMessageBox::Warning,"Copy status",fdc.getErrorStr());
85       mb.exec();
86     }
87   
88 }
89
90 void CopierThread::run()
91 {
92   _model->launchCopy();
93   FilesDirsCopier *par(qobject_cast<FilesDirsCopier *>(parent()));
94   if(!par)
95     return ;
96   emit par->myAcceptSignal(_model->getErrorStr().isEmpty());//emit signal to notify main thread. Not direct invocation because executed in different thread
97 }
98
99 void CopierThread::stopRequested()
100 {
101   _model->stopCurrentCopy();
102   requestInterruption();
103 }
104
105 QFilesDirsCopierModel::QFilesDirsCopierModel(QObject *parent2, const QList<const DataStructure *>& srcFiles, DataStructure *destLoc):QAbstractListModel(parent2),_srcFiles(srcFiles),_currentElt(0),_curProc(nullptr),_destLoc(destLoc),_progress(_srcFiles.size(),0)
106 {
107   QTableView *par(qobject_cast<QTableView *>(parent()));
108   par->resizeColumnToContents(0);
109   qRegisterMetaType<QVector<int>>();//to be able to send 3th arg on dataChanged signal
110 }
111
112 int QFilesDirsCopierModel::nbOfRows() const
113 {
114   return _srcFiles.size();
115 }
116
117 int QFilesDirsCopierModel::rowCount(const QModelIndex&) const
118 {
119   return nbOfRows();
120 }
121
122 int QFilesDirsCopierModel::columnCount(const QModelIndex&) const
123 {
124   return 1;
125 }
126
127 QVariant QFilesDirsCopierModel::data(const QModelIndex& index, int role) const
128 {
129   if(!index.isValid())
130     return QVariant();
131   if(role==Qt::DisplayRole || role==Qt::EditRole)
132     {
133       return _srcFiles[index.row()]->fullName();
134     }
135   return QVariant();
136 }
137
138 QVariant QFilesDirsCopierModel::headerData(int section, Qt::Orientation orientation, int role) const
139 {
140   if(role==Qt::DisplayRole || role==Qt::EditRole)
141     {
142       if(orientation==Qt::Horizontal)
143         {
144           return QString("Files to be copied");
145         }
146     }
147   return QAbstractListModel::headerData(section,orientation,role);
148 }
149
150 int QFilesDirsCopierModel::getProgressOf(int srcFileId) const
151 {
152   QMutexLocker locker(&_mutOnProc);
153   return _progress[srcFileId];
154 }
155
156 QString QFilesDirsCopierModel::getFullNameOf(int srcFileId) const
157 {
158   return _srcFiles[srcFileId]->fullName();
159 }
160
161 QString QFilesDirsCopierModel::getNameOf(int srcFileId) const
162 {
163   return _srcFiles[srcFileId]->name();
164 }
165
166 QString QFilesDirsCopierModel::getPrettyText(int srcFileId) const
167 {
168   int progress(getProgressOf(srcFileId));
169   QString name(getNameOf(srcFileId));
170   switch(progress)
171     {
172     case PROGRESS_STATUS_START:
173       return QString("Copy of %1 has started").arg(name);
174     case PROGRESS_STATUS_OVER:
175       return QString("Copy of %1 has finished").arg(name);
176     default:
177       return QString("%1 (%2%)").arg(name).arg(progress);
178     }
179 }
180
181 QSize QFilesDirsCopierModel::sizeHint() const
182 {
183   int w(0),h(0);
184   for(int zePos=0;zePos<_srcFiles.size();zePos++)
185     {
186       int progress(getProgressOf(zePos));
187       QString txt(getPrettyText(zePos));
188       QFont ft;
189       QFontMetrics fm(ft);
190       QSize sz(fm.boundingRect(txt).size());
191       w=std::max(sz.width(),w);
192       h+=sz.height();
193     }
194   return QSize(2*w,2*h);
195 }
196
197 void QFilesDirsCopierModel::launchCopy()
198 {
199   foreach(const DataStructure *srcFile,_srcFiles)
200     {
201       {
202         QMutexLocker locker(&_mutOnProc);
203         _progress[_currentElt]=PROGRESS_STATUS_START;
204         QModelIndex ind(this->index(_currentElt,0));
205         emit this->dataChanged(ind,ind,QVector<int>());
206       }
207       if(QThread::currentThread()->isInterruptionRequested())
208         {
209           _error=QString("%1 just before %2 (%3/%4)").arg(ATOMIC_STOP_MSG).arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size());
210           return ;
211         }
212       {
213         QMutexLocker locker(&_mutOnProc);
214         _curProc=new QProcess;
215         QStringList args;
216         args << "--progress" << "-r";
217         fillArgsForRSync(srcFile,args);
218         _curProc->start("rsync",args);
219         connect(_curProc,SIGNAL(readyReadStandardOutput()),this,SLOT(newOutputAvailable()));
220       }
221       bool s1(_curProc->waitForFinished(-1));
222       bool s2(_curProc->exitStatus()==QProcess::NormalExit && _curProc->exitCode()==0);
223       if(s1 && s2)
224         {
225           QMutexLocker locker(&_mutOnProc);
226           _progress[_currentElt]=PROGRESS_STATUS_OVER;
227           QModelIndex ind(this->index(_currentElt,0));
228           emit this->dataChanged(ind,ind,QVector<int>());
229         }
230       else
231         {
232           QMutexLocker locker(&_mutOnProc);
233           _error=QString("The copy of %1 has not finished correctly (%2/%3)").arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size());
234           return ;
235         }
236       if(QThread::currentThread()->isInterruptionRequested())
237         {
238           QMutexLocker locker(&_mutOnProc);
239           if(s1 && s2)
240             _error=QString("%1 right after %2 (%3/%4)").arg(ATOMIC_STOP_MSG).arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size());
241           else
242             _error=QString("Interrupted during copy of %1 (%2/%3)").arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size());
243           return ;
244         }
245       {
246         QMutexLocker locker(&_mutOnProc);
247         _curProc.clear();
248         _currentElt++;
249       }
250     }
251 }
252         
253 void QFilesDirsCopierModel::stopCurrentCopy()
254 {
255   QMutexLocker locker(&_mutOnProc);
256   if(!_curProc.isNull())
257     _curProc->kill();
258 }
259
260 void QFilesDirsCopierModel::newOutputAvailable()
261 {
262   QMutexLocker locker(&_mutOnProc);
263   QByteArray ba(_curProc->readAllStandardOutput());
264   QString str(QString::fromLocal8Bit(ba));
265   QRegularExpression re("[\\s]*([\\d\\,]+)[\\s]+([\\d]+)\\%[\\s]+([\\d]+\\.[\\d]{2}[Mk]B/s)[\\s]+([\\d]{1,2}:[\\d]{2}:[\\d]{2})");
266   QRegularExpressionMatch rem(re.match(str,0,QRegularExpression::PartialPreferFirstMatch));
267   if(rem.isValid())
268     {
269       QString s(rem.captured(2));
270       bool isOK(false);
271       int prog(s.toInt(&isOK));
272       if(isOK)
273         {
274           _progress[_currentElt]=prog;
275           QModelIndex ind(this->index(_currentElt,0));
276           emit this->dataChanged(ind,ind,QVector<int>());
277         }
278     }
279 }
280
281 void QFilesDirsCopierModel::fillArgsForRSync(const DataStructure *srcFile, QStringList& args) const
282 {
283   QString src(srcFile->entryForRSyncSrc()),dest(_destLoc->entryForRSyncDest());
284   args << src << dest;
285 }
286
287 void ProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
288 {
289   int zePos(index.row());
290   QWidget *wid( dynamic_cast<QWidget *>(painter->device()) );
291   int progress(_model->getProgressOf(zePos));
292   QString txt(_model->getPrettyText(zePos));
293   QRect refRect(option.rect);
294   QFont ft(wid->font());
295   QFontMetrics fm(ft);
296   QSize refRect2(fm.boundingRect(txt).size());
297   if(progress==QFilesDirsCopierModel::PROGRESS_STATUS_OVER)
298     {
299       QFont ft2(ft);
300       ft.setBold(true);
301       painter->setFont(ft);
302       QPen p(painter->pen());
303       painter->setPen(QPen(Qt::green));
304       painter->drawText(QPoint(refRect.x()+(refRect.width()-refRect2.width())/2,
305                                refRect.y()+refRect.height()/2+refRect2.height()/2-fm.descent()),txt);
306       painter->setPen(p);
307       painter->setFont(ft2);
308       return ;
309     }
310   if(progress==QFilesDirsCopierModel::PROGRESS_STATUS_START)
311     {
312       QFont ft2(ft);
313       ft.setBold(true);
314       painter->setFont(ft);
315       QPen p(painter->pen());
316       painter->setPen(QPen(Qt::red));
317       QString txt2(QString("Copy in progress of %1").arg(txt));
318       painter->drawText(QPoint(refRect.x()+(refRect.width()-refRect2.width())/2,
319                                refRect.y()+refRect.height()/2+refRect2.height()/2-fm.descent()),txt);
320       painter->setPen(p);
321       painter->setFont(ft2);
322       return ;
323     }
324   {
325     QBrush brush(Qt::green),b2(painter->brush());
326     painter->setBrush(brush);
327     painter->drawRect(refRect.x(),refRect.y(),int((float)refRect.width()*float(progress/100.f)),refRect.height());
328     painter->drawText(QPoint(refRect.x()+(refRect.width()-refRect2.width())/2,
329                              refRect.y()+refRect.height()/2+refRect2.height()/2-fm.descent()),txt);
330     painter->setBrush(b2);
331   }
332 }
333
334 CopierTableView::CopierTableView(QWidget *parent):QTableView(parent)
335 {
336   setSelectionMode(QAbstractItemView::NoSelection);
337   setFocusPolicy(Qt::NoFocus);
338 }
339
340 int CopierTableView::sizeHintForColumn(int column) const
341 {
342   return width();
343 }
344
345 int CopierTableView::sizeHintForRow(int row) const
346 {
347   QFilesDirsCopierModel *mod(qobject_cast<QFilesDirsCopierModel *>(model()));
348   int nbElts(mod->nbOfRows());
349   int sz((height()-horizontalHeader()->height())/(nbElts>0?nbElts:1));
350   return sz;
351 }
352
353 void CopierTableView::resizeEvent(QResizeEvent *event)
354 {
355   resizeColumnToContents(0);
356   resizeRowsToContents();
357   QTableView::resizeEvent(event);
358 }
359
360 QSize CopierTableView::sizeHint() const
361 {
362   QFilesDirsCopierModel *model2(qobject_cast<QFilesDirsCopierModel *>(model()));
363   if(model2)
364     return model2->sizeHint();
365   return CopierTableView::sizeHint();
366 }
367
368 FilesDirsCopier::FilesDirsCopier(QWidget *parent, const QList<const DataStructure *>& srcFiles, DataStructure *destLoc):QDialog(parent),_table(new CopierTableView(this)),_cancel(new QPushButton(this)),_model(nullptr)
369 {
370   QVBoxLayout *vb(new QVBoxLayout(this));
371   _cancel->setText("Cancel");
372   vb->addWidget(_table);
373   vb->addWidget(_cancel);
374   _model=new QFilesDirsCopierModel(_table,srcFiles,destLoc);
375   _th=new CopierThread(this,_model);
376   _table->setModel(_model);
377   _table->setShowGrid(false);
378   _table->setItemDelegate(new ProgressDelegate(_table,_model));
379   connect(_cancel,SIGNAL(clicked()),this,SLOT(cancelRequested()));
380   connect(this,SIGNAL(myAcceptSignal(bool)),this,SLOT(myAccept(bool)));
381 }
382
383 void FilesDirsCopier::cancelRequested()
384 {
385   _th->stopRequested();
386   _th->wait();
387   reject();
388 }
389
390 void FilesDirsCopier::myAccept(bool isOK)
391 {
392   _th->wait();
393   if(isOK)
394     accept();
395   else
396     reject();
397 }
398
399 int FilesDirsCopier::exec()
400 {
401   _th->start();
402   return QDialog::exec();
403 }