1 /***************************************************************************
2     qgsgrassimport.cpp  -  Import to GRASS mapset
3                              -------------------
4     begin                : May, 2015
5     copyright            : (C) 2015 Radim Blazek
6     email                : radim.blazek@gmail.com
7  ***************************************************************************/
8 /***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include <QByteArray>
18 #include <QtConcurrentRun>
19 
20 #include "qgscoordinatereferencesystem.h"
21 #include "qgscoordinatetransform.h"
22 #include "qgsfeature.h"
23 #include "qgsfeatureiterator.h"
24 #include "qgsgeometry.h"
25 #include "qgsproviderregistry.h"
26 #include "qgsrasterdataprovider.h"
27 #include "qgsrasteriterator.h"
28 
29 #include "qgsgrassimport.h"
30 
31 extern "C"
32 {
33 #include <grass/version.h>
34 #include <grass/gis.h>
35 #include <grass/raster.h>
36 #include <grass/imagery.h>
37 }
38 
39 //------------------------------ QgsGrassImport ------------------------------------
instance()40 QgsGrassImportIcon *QgsGrassImportIcon::instance()
41 {
42   static QgsGrassImportIcon *sInstance = new QgsGrassImportIcon();
43   return sInstance;
44 }
45 
QgsGrassImportIcon()46 QgsGrassImportIcon::QgsGrassImportIcon()
47   : QgsAnimatedIcon( QgsApplication::iconPath( QStringLiteral( "/mIconImport.gif" ) ) )
48 {
49 }
50 
51 //------------------------------ QgsGrassImportProcess ------------------------------------
QgsGrassImportProgress(QProcess * process,QObject * parent)52 QgsGrassImportProgress::QgsGrassImportProgress( QProcess *process, QObject *parent )
53   : QObject( parent )
54   , mProcess( process )
55   , mProgressMin( 0 )
56   , mProgressMax( 0 )
57   , mProgressValue( 0 )
58 {
59   connect( mProcess, &QProcess::readyReadStandardError, this, &QgsGrassImportProgress::onReadyReadStandardError );
60 }
61 
setProcess(QProcess * process)62 void QgsGrassImportProgress::setProcess( QProcess *process )
63 {
64   mProcess = process;
65   connect( mProcess, &QProcess::readyReadStandardError, this, &QgsGrassImportProgress::onReadyReadStandardError );
66 }
67 
onReadyReadStandardError()68 void QgsGrassImportProgress::onReadyReadStandardError()
69 {
70   if ( mProcess )
71   {
72     // TODO: parse better progress output
73     QString output = QString::fromLocal8Bit( mProcess->readAllStandardError() );
74     Q_FOREACH ( const QString &line, output.split( "\n" ) )
75     {
76       QgsDebugMsg( "line = '" + line + "'" );
77       QString text, html;
78       int value;
79       QgsGrass::ModuleOutput type = QgsGrass::parseModuleOutput( line, text, html, value );
80       if ( type == QgsGrass::OutputPercent )
81       {
82         mProgressMin = 0;
83         mProgressMax = 100;
84         mProgressValue = value;
85         emit progressChanged( html, mProgressHtml, mProgressMin, mProgressMax, mProgressValue );
86       }
87       else if ( type == QgsGrass::OutputProgress )
88       {
89         html = tr( "Progress: %1" ).arg( value );
90         append( html );
91       }
92       else if ( type == QgsGrass::OutputMessage || type == QgsGrass::OutputWarning || type == QgsGrass::OutputError )
93       {
94         append( html );
95       }
96     }
97   }
98 }
99 
append(const QString & html)100 void QgsGrassImportProgress::append( const QString &html )
101 {
102   QgsDebugMsg( "html = " + html );
103   if ( !mProgressHtml.isEmpty() )
104   {
105     mProgressHtml += QLatin1String( "<br>" );
106   }
107   mProgressHtml += html;
108   emit progressChanged( html, mProgressHtml, mProgressMin, mProgressMax, mProgressValue );
109 }
110 
setRange(int min,int max)111 void QgsGrassImportProgress::setRange( int min, int max )
112 {
113   mProgressMin = min;
114   mProgressMax = max;
115   mProgressValue = min;
116   emit progressChanged( QString(), mProgressHtml, mProgressMin, mProgressMax, mProgressValue );
117 }
118 
setValue(int value)119 void QgsGrassImportProgress::setValue( int value )
120 {
121   mProgressValue = value;
122   emit progressChanged( QString(), mProgressHtml, mProgressMin, mProgressMax, mProgressValue );
123 }
124 
125 //------------------------------ QgsGrassImport ------------------------------------
QgsGrassImport(const QgsGrassObject & grassObject)126 QgsGrassImport::QgsGrassImport( const QgsGrassObject &grassObject )
127   : mGrassObject( grassObject )
128   , mCanceled( false )
129 {
130   // QMovie used by QgsAnimatedIcon is using QTimer which cannot be start from another thread
131   // (it works on Linux however) so we cannot start it connecting from QgsGrassImportItem and
132   // connect it also here (QgsGrassImport is constructed on the main thread) to a slot doing nothing.
133   QgsGrassImportIcon::instance()->connectFrameChanged( this, &QgsGrassImport::frameChanged );
134 }
135 
~QgsGrassImport()136 QgsGrassImport::~QgsGrassImport()
137 {
138   if ( mFutureWatcher && !mFutureWatcher->isFinished() )
139   {
140     QgsDebugMsg( "mFutureWatcher not finished -> waitForFinished()" );
141     mFutureWatcher->waitForFinished();
142   }
143   QgsGrassImportIcon::instance()->disconnectFrameChanged( this, &QgsGrassImport::frameChanged );
144 }
145 
setError(const QString & error)146 void QgsGrassImport::setError( const QString &error )
147 {
148   QgsDebugMsg( "error: " + error );
149   mError = error;
150 }
151 
error()152 QString QgsGrassImport::error()
153 {
154   return mError;
155 }
156 
importInThread()157 void QgsGrassImport::importInThread()
158 {
159   mFutureWatcher = new QFutureWatcher<bool>( this );
160   connect( mFutureWatcher, &QFutureWatcherBase::finished, this, &QgsGrassImport::onFinished );
161   mFutureWatcher->setFuture( QtConcurrent::run( run, this ) );
162 }
163 
run(QgsGrassImport * imp)164 bool QgsGrassImport::run( QgsGrassImport *imp )
165 {
166   imp->import();
167   return true;
168 }
169 
onFinished()170 void QgsGrassImport::onFinished()
171 {
172   emit finished( this );
173 }
174 
names() const175 QStringList QgsGrassImport::names() const
176 {
177   QStringList list;
178   list << mGrassObject.name();
179   return list;
180 }
181 
isCanceled() const182 bool QgsGrassImport::isCanceled() const
183 {
184   return mCanceled;
185 }
186 
cancel()187 void QgsGrassImport::cancel()
188 {
189   mCanceled = true;
190 }
191 
192 //------------------------------ QgsGrassRasterImport ------------------------------------
QgsGrassRasterImport(QgsRasterPipe * pipe,const QgsGrassObject & grassObject,const QgsRectangle & extent,int xSize,int ySize)193 QgsGrassRasterImport::QgsGrassRasterImport( QgsRasterPipe *pipe, const QgsGrassObject &grassObject,
194     const QgsRectangle &extent, int xSize, int ySize )
195   : QgsGrassImport( grassObject )
196   , mPipe( pipe )
197   , mExtent( extent )
198   , mXSize( xSize )
199   , mYSize( ySize )
200 {
201 }
202 
~QgsGrassRasterImport()203 QgsGrassRasterImport::~QgsGrassRasterImport()
204 {
205   if ( mFutureWatcher && !mFutureWatcher->isFinished() )
206   {
207     QgsDebugMsg( "mFutureWatcher not finished -> waitForFinished()" );
208     mFutureWatcher->waitForFinished();
209   }
210   delete mPipe;
211 }
212 
import()213 bool QgsGrassRasterImport::import()
214 {
215   if ( !mPipe )
216   {
217     setError( QStringLiteral( "Pipe is null." ) );
218     return false;
219   }
220 
221   QgsRasterDataProvider *provider = mPipe->provider();
222   if ( !provider )
223   {
224     setError( QStringLiteral( "Pipe has no provider." ) );
225     return false;
226   }
227 
228   if ( !provider->isValid() )
229   {
230     setError( QStringLiteral( "Provider is not valid." ) );
231     return false;
232   }
233 
234 
235   struct Cell_head defaultWindow;
236   if ( !QgsGrass::defaultRegion( mGrassObject.gisdbase(), mGrassObject.location(), &defaultWindow ) )
237   {
238     setError( QStringLiteral( "Cannot get default window" ) );
239     return false;
240   }
241 
242   int redBand = 0;
243   int greenBand = 0;
244   int blueBand = 0;
245   for ( int band = 1; band <= provider->bandCount(); band++ )
246   {
247     QgsDebugMsg( QString( "band = %1" ).arg( band ) );
248     int colorInterpretation = provider->colorInterpretation( band );
249     if ( colorInterpretation == QgsRaster::RedBand )
250     {
251       redBand = band;
252     }
253     else if ( colorInterpretation == QgsRaster::GreenBand )
254     {
255       greenBand = band;
256     }
257     else if ( colorInterpretation == QgsRaster::BlueBand )
258     {
259       blueBand = band;
260     }
261 
262     Qgis::DataType qgis_out_type = Qgis::UnknownDataType;
263 #ifdef QGISDEBUG
264     RASTER_MAP_TYPE data_type = -1;
265 #endif
266     switch ( provider->dataType( band ) )
267     {
268       case Qgis::Byte:
269       case Qgis::UInt16:
270       case Qgis::Int16:
271       case Qgis::UInt32:
272       case Qgis::Int32:
273         qgis_out_type = Qgis::Int32;
274         break;
275       case Qgis::Float32:
276         qgis_out_type = Qgis::Float32;
277         break;
278       case Qgis::Float64:
279         qgis_out_type = Qgis::Float64;
280         break;
281       case Qgis::ARGB32:
282       case Qgis::ARGB32_Premultiplied:
283         qgis_out_type = Qgis::Int32;  // split to multiple bands?
284         break;
285       case Qgis::CInt16:
286       case Qgis::CInt32:
287       case Qgis::CFloat32:
288       case Qgis::CFloat64:
289       case Qgis::UnknownDataType:
290         setError( tr( "Data type %1 not supported" ).arg( provider->dataType( band ) ) );
291         return false;
292     }
293 
294     QgsDebugMsg( QString( "data_type = %1" ).arg( data_type ) );
295 
296     QString module = QgsGrass::qgisGrassModulePath() + "/qgis.r.in";
297     QStringList arguments;
298     QString name = mGrassObject.name();
299     if ( provider->bandCount() > 1 )
300     {
301       // raster.<band> to keep in sync with r.in.gdal
302       name += QStringLiteral( ".%1" ).arg( band );
303     }
304     arguments.append( "output=" + name );    // get list of all output names
305     QTemporaryFile gisrcFile;
306     try
307     {
308       mProcess = QgsGrass::startModule( mGrassObject.gisdbase(), mGrassObject.location(), mGrassObject.mapset(), module, arguments, gisrcFile );
309     }
310     catch ( QgsGrass::Exception &e )
311     {
312       setError( e.what() );
313       return false;
314     }
315     if ( !mProgress )
316     {
317       mProgress = new QgsGrassImportProgress( mProcess, this );
318     }
319     else
320     {
321       mProgress->setProcess( mProcess );
322     }
323     mProgress->append( tr( "Writing band %1/%2" ).arg( band ).arg( provider->bandCount() ) );
324 
325     QDataStream outStream( mProcess );
326 
327     outStream << ( qint32 ) defaultWindow.proj;
328     outStream << ( qint32 ) defaultWindow.zone;
329     outStream << mExtent << ( qint32 )mXSize << ( qint32 )mYSize;
330     outStream << ( qint32 )qgis_out_type;
331 
332     // calculate reasonable block size (5MB)
333     int maximumTileHeight = 5000000 / mXSize;
334     maximumTileHeight = std::max( 1, maximumTileHeight );
335     // smaller if reprojecting so that it can be canceled quickly
336     if ( mPipe->projector() )
337     {
338       maximumTileHeight = std::max( 1, 100000 / mXSize );
339     }
340 
341     QgsRasterIterator iter( mPipe->last() );
342     iter.setMaximumTileWidth( mXSize );
343     iter.setMaximumTileHeight( maximumTileHeight );
344 
345     iter.startRasterRead( band, mXSize, mYSize, mExtent );
346 
347     int iterLeft = 0;
348     int iterTop = 0;
349     int iterCols = 0;
350     int iterRows = 0;
351     QgsRasterBlock *block = nullptr;
352     mProcess->setReadChannel( QProcess::StandardOutput );
353     mProgress->setRange( 0, mYSize - 1 );
354     while ( iter.readNextRasterPart( band, iterCols, iterRows, &block, iterLeft, iterTop ) )
355     {
356       for ( int row = 0; row < iterRows; row++ )
357       {
358         mProgress->setValue( iterTop + row );
359 
360         if ( !block->convert( qgis_out_type ) )
361         {
362           setError( tr( "Cannot convert block (%1) to data type %2" ).arg( block->toString() ).arg( qgis_out_type ) );
363           delete block;
364           return false;
365         }
366         // prepare null values
367         double noDataValue;
368         if ( block->hasNoDataValue() )
369         {
370           noDataValue = block->noDataValue();
371         }
372         else
373         {
374           switch ( qgis_out_type )
375           {
376             case Qgis::Int32:
377               noDataValue = -2147483648.0;
378               break;
379             case Qgis::Float32:
380               noDataValue = std::numeric_limits<float>::max() * -1.0;
381               break;
382             case Qgis::Float64:
383               noDataValue = std::numeric_limits<double>::max() * -1.0;
384               break;
385             default: // should not happen
386               noDataValue = std::numeric_limits<double>::max() * -1.0;
387           }
388           for ( qgssize i = 0; i < ( qgssize )block->width()*block->height(); i++ )
389           {
390             if ( block->isNoData( i ) )
391             {
392               block->setValue( i, noDataValue );
393             }
394           }
395         }
396 
397         char *data = block->bits( row, 0 );
398         int size = iterCols * block->dataTypeSize();
399         QByteArray byteArray = QByteArray::fromRawData( data, size ); // does not copy data and does not take ownership
400         if ( isCanceled() )
401         {
402           outStream << true; // cancel module
403           break;
404         }
405         outStream << false; // not canceled
406         outStream << noDataValue;
407 
408         outStream << byteArray;
409 
410         // Without waitForBytesWritten() it does not finish OK on Windows (process timeout)
411         mProcess->waitForBytesWritten( -1 );
412 
413 #ifndef Q_OS_WIN
414         // wait until the row is written to allow quick cancel (don't send data to buffer)
415         mProcess->waitForReadyRead();
416         bool result;
417         outStream >> result;
418 #endif
419       }
420       delete block;
421       if ( isCanceled() )
422       {
423         outStream << true; // cancel module
424         break;
425       }
426     }
427 
428     // TODO: send something back from module and read it here to close map correctly in module
429 
430     mProcess->closeWriteChannel();
431     // TODO: best timeout?
432     mProcess->waitForFinished( 30000 );
433 
434     QString stderrString = mProcess->readAllStandardError().constData();
435 
436 #ifdef QGISDEBUG
437     QString stdoutString = mProcess->readAllStandardOutput().constData();
438     QString processResult = QStringLiteral( "exitStatus=%1, exitCode=%2, error=%3, errorString=%4 stdout=%5, stderr=%6" )
439                             .arg( mProcess->exitStatus() ).arg( mProcess->exitCode() )
440                             .arg( mProcess->error() ).arg( mProcess->errorString(),
441                                 stdoutString.replace( QLatin1String( "\n" ), QLatin1String( ", " ) ), stderrString.replace( QLatin1String( "\n" ), QLatin1String( ", " ) ) );
442     QgsDebugMsg( "processResult: " + processResult );
443 #endif
444 
445     if ( mProcess->exitStatus() != QProcess::NormalExit )
446     {
447       setError( mProcess->errorString() );
448       mProcess = nullptr;
449       return false;
450     }
451 
452     if ( mProcess->exitCode() != 0 )
453     {
454       setError( stderrString );
455       delete mProcess;
456       mProcess = nullptr;
457       return false;
458     }
459 
460     delete mProcess;
461     mProcess = nullptr;
462   }
463   QgsDebugMsg( QString( "redBand = %1 greenBand = %2 blueBand = %3" ).arg( redBand ).arg( greenBand ).arg( blueBand ) );
464   if ( redBand > 0 && greenBand > 0 && blueBand > 0 )
465   {
466     // TODO: check if the group exists
467     // I_find_group()
468     QString name = mGrassObject.name();
469 
470     G_TRY
471     {
472       QgsGrass::setMapset( mGrassObject.gisdbase(), mGrassObject.location(), mGrassObject.mapset() );
473       struct Ref ref;
474       I_get_group_ref( name.toUtf8().constData(), &ref );
475       QString redName = name + QStringLiteral( ".%1" ).arg( redBand );
476       QString greenName = name + QStringLiteral( ".%1" ).arg( greenBand );
477       QString blueName = name + QStringLiteral( ".%1" ).arg( blueBand );
478       I_add_file_to_group_ref( redName.toUtf8().constData(), mGrassObject.mapset().toUtf8().constData(), &ref );
479       I_add_file_to_group_ref( greenName.toUtf8().constData(), mGrassObject.mapset().toUtf8().constData(), &ref );
480       I_add_file_to_group_ref( blueName.toUtf8().constData(), mGrassObject.mapset().toUtf8().constData(), &ref );
481       I_put_group_ref( name.toUtf8().constData(), &ref );
482     }
483     G_CATCH( QgsGrass::Exception & e )
484     {
485       QgsDebugMsg( QString( "Cannot create group: %1" ).arg( e.what() ) );
486     }
487   }
488   return true;
489 }
490 
srcDescription() const491 QString QgsGrassRasterImport::srcDescription() const
492 {
493   if ( !mPipe || !mPipe->provider() )
494   {
495     return QString();
496   }
497   return mPipe->provider()->dataSourceUri();
498 }
499 
extensions(QgsRasterDataProvider * provider)500 QStringList QgsGrassRasterImport::extensions( QgsRasterDataProvider *provider )
501 {
502   QStringList list;
503   if ( provider && provider->bandCount() > 1 )
504   {
505     int bands = provider->bandCount();
506     list.reserve( bands );
507     for ( int band = 1; band <= bands; ++band )
508     {
509       list << QStringLiteral( ".%1" ).arg( band );
510     }
511   }
512   return list;
513 }
514 
names() const515 QStringList QgsGrassRasterImport::names() const
516 {
517   QStringList list;
518   if ( mPipe && mPipe->provider() )
519   {
520     Q_FOREACH ( const QString &ext, extensions( mPipe->provider() ) )
521     {
522       list << mGrassObject.name() + ext;
523     }
524   }
525   if ( list.isEmpty() )
526   {
527     list << mGrassObject.name();
528   }
529   return list;
530 }
531 
532 //------------------------------ QgsGrassVectorImport ------------------------------------
QgsGrassVectorImport(QgsVectorDataProvider * provider,const QgsGrassObject & grassObject)533 QgsGrassVectorImport::QgsGrassVectorImport( QgsVectorDataProvider *provider, const QgsGrassObject &grassObject )
534   : QgsGrassImport( grassObject )
535   , mProvider( provider )
536 {
537 }
538 
~QgsGrassVectorImport()539 QgsGrassVectorImport::~QgsGrassVectorImport()
540 {
541   if ( mFutureWatcher && !mFutureWatcher->isFinished() )
542   {
543     QgsDebugMsg( "mFutureWatcher not finished -> waitForFinished()" );
544     mFutureWatcher->waitForFinished();
545   }
546   delete mProvider;
547 }
548 
import()549 bool QgsGrassVectorImport::import()
550 {
551 
552   if ( !mProvider )
553   {
554     setError( QStringLiteral( "Provider is null." ) );
555     return false;
556   }
557 
558   if ( !mProvider->isValid() )
559   {
560     setError( QStringLiteral( "Provider is not valid." ) );
561     return false;
562   }
563 
564   QgsCoordinateReferenceSystem providerCrs = mProvider->crs();
565   QgsCoordinateReferenceSystem mapsetCrs = QgsGrass::crsDirect( mGrassObject.gisdbase(), mGrassObject.location() );
566   QgsDebugMsg( "providerCrs = " + providerCrs.toWkt() );
567   QgsDebugMsg( "mapsetCrs = " + mapsetCrs.toWkt() );
568   QgsCoordinateTransform coordinateTransform;
569   bool doTransform = false;
570   if ( providerCrs.isValid() && mapsetCrs.isValid() && providerCrs != mapsetCrs )
571   {
572     coordinateTransform.setSourceCrs( providerCrs );
573     coordinateTransform.setDestinationCrs( mapsetCrs );
574     doTransform = true;
575   }
576 
577   QString module = QgsGrass::qgisGrassModulePath() + "/qgis.v.in";
578   QStringList arguments;
579   QString name = mGrassObject.name();
580   arguments.append( "output=" + name );
581   QTemporaryFile gisrcFile;
582   try
583   {
584     mProcess = QgsGrass::startModule( mGrassObject.gisdbase(), mGrassObject.location(), mGrassObject.mapset(), module, arguments, gisrcFile );
585   }
586   catch ( QgsGrass::Exception &e )
587   {
588     setError( e.what() );
589     return false;
590   }
591   mProgress = new QgsGrassImportProgress( mProcess, this );
592 
593   QDataStream outStream( mProcess );
594   mProcess->setReadChannel( QProcess::StandardOutput );
595 
596   QgsWkbTypes::Type wkbType = mProvider->wkbType();
597   bool isPolygon = QgsWkbTypes::singleType( QgsWkbTypes::flatType( wkbType ) ) == QgsWkbTypes::Polygon;
598   outStream << ( qint32 )wkbType;
599 
600   outStream << mProvider->fields();
601 
602   // FID may be 0, but cat should be >= 1 -> check if fid 0 exists
603   qint32 fidToCatPlus = 0;
604   QgsFeature feature;
605   QgsFeatureIterator iterator = mProvider->getFeatures( QgsFeatureRequest().setFilterFid( 0 ) );
606   if ( iterator.nextFeature( feature ) )
607   {
608     fidToCatPlus = 1;
609   }
610   iterator.close();
611   outStream << fidToCatPlus;
612 
613   qint32 featureCount = mProvider->featureCount();
614   outStream << featureCount;
615 
616   mProgress->append( tr( "Writing features" ) );
617   for ( int i = 0; i < ( isPolygon ? 2 : 1 ); i++ ) // two cycles with polygons
618   {
619     iterator = mProvider->getFeatures();
620     // rewind does not work
621 #if 0
622     if ( i > 0 ) // second run for polygons
623     {
624       iterator.rewind();
625     }
626 #endif
627     QgsDebugMsg( "send features" );
628     // Better to get real progress from module (without buffer)
629 #if 0
630     mProgress->setRange( 1, featureCount );
631 #endif
632     int count = 0;
633     while ( iterator.nextFeature( feature ) )
634     {
635       if ( !feature.isValid() )
636       {
637         continue;
638       }
639       if ( doTransform && feature.hasGeometry() )
640       {
641         QgsGeometry g = feature.geometry();
642         g.transform( coordinateTransform );
643         feature.setGeometry( g );
644       }
645       if ( isCanceled() )
646       {
647         outStream << true; // cancel module
648         break;
649       }
650       outStream << false; // not canceled
651       outStream << feature;
652 
653       // Without waitForBytesWritten() it does not finish OK on Windows (data lost)
654       mProcess->waitForBytesWritten( -1 );
655 
656 #ifndef Q_OS_WIN
657       // wait until the feature is written to allow quick cancel (don't send data to buffer)
658 
659       // Feedback disabled because it was sometimes hanging on Linux, for example, when importing polygons
660       // the features were written OK in the first run, but after cleaning of polygons, which takes some time
661       // it was hanging here for few seconds after each feature, but data did not arrive to the modulee anyway,
662       // QFSFileEnginePrivate::nativeRead() was hanging on fgetc()
663 
664       // TODO: inspect what is happening in QProcess, if there is some buffer and how to disable it
665 #if 0
666       mProcess->waitForReadyRead();
667       bool result;
668       outStream >> result;
669 #endif
670 #endif
671       count++;
672 #if 0
673       if ( count % 100 == 0 )
674       {
675         mProgress->setValue( count );
676       }
677 #endif
678       // get some feedback for large datasets
679       if ( count % 10000 == 0 )
680       {
681         QgsDebugMsg( QString( "%1 features written" ).arg( count ) );
682       }
683     }
684 
685     feature = QgsFeature(); // indicate end by invalid feature
686     outStream << false; // not canceled
687     outStream << feature;
688 
689     mProcess->waitForBytesWritten( -1 );
690     QgsDebugMsg( "features sent" );
691 #ifndef Q_OS_WIN
692 #if 0
693     mProcess->waitForReadyRead();
694     bool result;
695     outStream >> result;
696     QgsDebugMsg( "got feedback" );
697 #endif
698 #endif
699     iterator.close();
700   }
701 
702   // Close write channel before waiting for response to avoid stdin buffer problem on Windows
703   mProcess->closeWriteChannel();
704 
705   QgsDebugMsg( "waitForReadyRead" );
706   bool result;
707   mProcess->waitForReadyRead();
708   outStream >> result;
709   QgsDebugMsg( QString( "result = %1" ).arg( result ) );
710 
711   QgsDebugMsg( "waitForFinished" );
712   mProcess->waitForFinished( 30000 );
713 
714 #ifdef QGISDEBUG
715   QString stdoutString = mProcess->readAllStandardOutput().constData();
716 #endif
717   QString stderrString = mProcess->readAllStandardError().constData();
718 
719 #ifdef QGISDEBUG
720   QString processResult = QStringLiteral( "exitStatus=%1, exitCode=%2, error=%3, errorString=%4 stdout=%5, stderr=%6" )
721                           .arg( mProcess->exitStatus() ).arg( mProcess->exitCode() )
722                           .arg( mProcess->error() ).arg( mProcess->errorString(),
723                               stdoutString.replace( QLatin1String( "\n" ), QLatin1String( ", " ) ), stderrString.replace( QLatin1String( "\n" ), QLatin1String( ", " ) ) );
724   QgsDebugMsg( "processResult: " + processResult );
725 #endif
726 
727   if ( mProcess->exitStatus() != QProcess::NormalExit )
728   {
729     setError( mProcess->errorString() );
730     delete mProcess;
731     mProcess = nullptr;
732     return false;
733   }
734 
735   if ( mProcess->exitCode() != 0 )
736   {
737     setError( stderrString );
738     delete mProcess;
739     mProcess = nullptr;
740     return false;
741   }
742 
743   delete mProcess;
744   mProcess = nullptr;
745   return true;
746 }
747 
srcDescription() const748 QString QgsGrassVectorImport::srcDescription() const
749 {
750   if ( !mProvider )
751   {
752     return QString();
753   }
754   return mProvider->dataSourceUri();
755 }
756 
757 //------------------------------ QgsGrassCopy ------------------------------------
QgsGrassCopy(const QgsGrassObject & srcObject,const QgsGrassObject & destObject)758 QgsGrassCopy::QgsGrassCopy( const QgsGrassObject &srcObject, const QgsGrassObject &destObject )
759   : QgsGrassImport( destObject )
760   , mSrcObject( srcObject )
761 {
762 }
763 
import()764 bool QgsGrassCopy::import()
765 {
766 
767   try
768   {
769     QgsGrass::copyObject( mSrcObject, mGrassObject );
770   }
771   catch ( QgsGrass::Exception &e )
772   {
773     setError( e.what() );
774     return false;
775   }
776 
777   return true;
778 }
779 
780 
srcDescription() const781 QString QgsGrassCopy::srcDescription() const
782 {
783   return mSrcObject.toString();
784 }
785 
786 //------------------------------ QgsGrassExternal ------------------------------------
QgsGrassExternal(const QString & gdalSource,const QgsGrassObject & destObject)787 QgsGrassExternal::QgsGrassExternal( const QString &gdalSource, const QgsGrassObject &destObject )
788   : QgsGrassImport( destObject )
789   , mSource( gdalSource )
790 {
791 }
792 
import()793 bool QgsGrassExternal::import()
794 {
795 
796   try
797   {
798     QString cmd = QgsGrass::gisbase() + "/bin/r.external";
799     QStringList arguments;
800 
801     if ( QFileInfo::exists( mSource ) )
802     {
803       arguments << "input=" + mSource;
804     }
805     else
806     {
807       arguments << "source=" + mSource;
808     }
809     arguments << "output=" + mGrassObject.name();
810 
811     // TODO: best timeout
812     int timeout = -1;
813     // throws QgsGrass::Exception
814     QgsGrass::runModule( mGrassObject.gisdbase(), mGrassObject.location(), mGrassObject.mapset(), cmd, arguments, timeout, false );
815   }
816   catch ( QgsGrass::Exception &e )
817   {
818     setError( e.what() );
819     return false;
820   }
821 
822   return true;
823 }
824 
srcDescription() const825 QString QgsGrassExternal::srcDescription() const
826 {
827   return mSource;
828 }
829