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