1 /***************************************************************************
2       qgsarcgisrestdataitems.cpp
3       -----------------
4     begin                : December 2020
5     copyright            : (C) 2020 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 #include "qgsarcgisrestdataitems.h"
16 #include "qgslogger.h"
17 #include "qgsowsconnection.h"
18 #include "qgsarcgisrestutils.h"
19 #include "qgsarcgisrestquery.h"
20 #include "qgsarcgisportalutils.h"
21 #include "qgsdataprovider.h"
22 
23 #ifdef HAVE_GUI
24 #include "qgsarcgisrestsourceselect.h"
25 #endif
26 
27 #include <QMessageBox>
28 #include <QCoreApplication>
29 #include <QSettings>
30 #include <QUrl>
31 #include "qgssettings.h"
32 
33 
34 //
35 // QgsArcGisRestRootItem
36 //
37 
QgsArcGisRestRootItem(QgsDataItem * parent,const QString & name,const QString & path)38 QgsArcGisRestRootItem::QgsArcGisRestRootItem( QgsDataItem *parent, const QString &name, const QString &path )
39   : QgsConnectionsRootItem( parent, name, path, QStringLiteral( "AFS" ) )
40 {
41   mCapabilities |= Qgis::BrowserItemCapability::Fast;
42   mIconName = QStringLiteral( "mIconAfs.svg" );
43   populate();
44 }
45 
createChildren()46 QVector<QgsDataItem *> QgsArcGisRestRootItem::createChildren()
47 {
48   QVector<QgsDataItem *> connections;
49 
50   const QStringList connectionList = QgsOwsConnection::connectionList( QStringLiteral( "ARCGISFEATURESERVER" ) );
51   for ( const QString &connName : connectionList )
52   {
53     const QString path = QStringLiteral( "afs:/" ) + connName;
54     connections.append( new QgsArcGisRestConnectionItem( this, connName, path, connName ) );
55   }
56 
57   return connections;
58 }
59 
60 #ifdef HAVE_GUI
paramWidget()61 QWidget *QgsArcGisRestRootItem::paramWidget()
62 {
63   QgsArcGisRestSourceSelect *select = new QgsArcGisRestSourceSelect( nullptr, Qt::WindowFlags(), QgsProviderRegistry::WidgetMode::Manager );
64   connect( select, &QgsArcGisRestSourceSelect::connectionsChanged, this, &QgsArcGisRestRootItem::onConnectionsChanged );
65   return select;
66 }
67 
onConnectionsChanged()68 void QgsArcGisRestRootItem::onConnectionsChanged()
69 {
70   refresh();
71 }
72 #endif
73 
74 ///////////////////////////////////////////////////////////////////////////////
75 
addFolderItems(QVector<QgsDataItem * > & items,const QVariantMap & serviceData,const QString & baseUrl,const QString & authcfg,const QgsStringMap & headers,QgsDataItem * parent,const QString & supportedFormats)76 void addFolderItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceData, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers, QgsDataItem *parent,
77                      const QString &supportedFormats )
78 {
79   QgsArcGisRestQueryUtils::visitFolderItems( [parent, &baseUrl, &items, headers, authcfg, supportedFormats]( const QString & name, const QString & url )
80   {
81     std::unique_ptr< QgsArcGisRestFolderItem > folderItem = std::make_unique< QgsArcGisRestFolderItem >( parent, name, url, baseUrl, authcfg, headers );
82     folderItem->setSupportedFormats( supportedFormats );
83     items.append( folderItem.release() );
84   }, serviceData, baseUrl );
85 }
86 
addServiceItems(QVector<QgsDataItem * > & items,const QVariantMap & serviceData,const QString & baseUrl,const QString & authcfg,const QgsStringMap & headers,QgsDataItem * parent,const QString & supportedFormats)87 void addServiceItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceData, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers, QgsDataItem *parent,
88                       const QString &supportedFormats )
89 {
90   QgsArcGisRestQueryUtils::visitServiceItems(
91     [&items, parent, authcfg, headers, supportedFormats]( const QString & name, const QString & url, const QString & service, QgsArcGisRestQueryUtils::ServiceTypeFilter serviceType )
92   {
93     switch ( serviceType )
94     {
95       case QgsArcGisRestQueryUtils::Raster:
96       {
97         std::unique_ptr< QgsArcGisMapServiceItem > serviceItem = std::make_unique< QgsArcGisMapServiceItem >( parent, name, url, url, authcfg, headers, service );
98         items.append( serviceItem.release() );
99         break;
100       }
101 
102       case QgsArcGisRestQueryUtils::Vector:
103       {
104         std::unique_ptr< QgsArcGisFeatureServiceItem > serviceItem = std::make_unique< QgsArcGisFeatureServiceItem >( parent, name, url, url, authcfg, headers );
105         serviceItem->setSupportedFormats( supportedFormats );
106         items.append( serviceItem.release() );
107         break;
108       }
109 
110       case QgsArcGisRestQueryUtils::AllTypes:
111         break;
112     }
113   }, serviceData, baseUrl );
114 }
115 
addLayerItems(QVector<QgsDataItem * > & items,const QVariantMap & serviceData,const QString & parentUrl,const QString & authcfg,const QgsStringMap & headers,QgsDataItem * parent,QgsArcGisRestQueryUtils::ServiceTypeFilter serviceTypeFilter,const QString & supportedFormats)116 void addLayerItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceData, const QString &parentUrl, const QString &authcfg, const QgsStringMap &headers, QgsDataItem *parent, QgsArcGisRestQueryUtils::ServiceTypeFilter serviceTypeFilter,
117                     const QString &supportedFormats )
118 {
119   QMultiMap< QString, QgsDataItem * > layerItems;
120   QMap< QString, QString > parents;
121 
122   QgsArcGisRestQueryUtils::addLayerItems( [parent, &layerItems, &parents, authcfg, headers, serviceTypeFilter, supportedFormats]( const QString & parentLayerId, QgsArcGisRestQueryUtils::ServiceTypeFilter serviceType, QgsWkbTypes::GeometryType geometryType, const QString & id, const QString & name, const QString & description, const QString & url, bool isParent, const QString & authid, const QString & format )
123   {
124     Q_UNUSED( description )
125 
126     if ( !parentLayerId.isEmpty() )
127       parents.insert( id, parentLayerId );
128 
129     if ( isParent && serviceType != QgsArcGisRestQueryUtils::Raster )
130     {
131       if ( !layerItems.value( id ) )
132       {
133         std::unique_ptr< QgsArcGisRestParentLayerItem > layerItem = std::make_unique< QgsArcGisRestParentLayerItem >( parent, name, url, authcfg, headers );
134         layerItems.insert( id, layerItem.release() );
135       }
136     }
137     else
138     {
139       std::unique_ptr< QgsDataItem > layerItem;
140       switch ( serviceTypeFilter == QgsArcGisRestQueryUtils::AllTypes ? serviceType : serviceTypeFilter )
141       {
142         case QgsArcGisRestQueryUtils::Vector:
143           layerItem = std::make_unique< QgsArcGisFeatureServiceLayerItem >( parent, name, url, name, authid, authcfg, headers, geometryType == QgsWkbTypes::PolygonGeometry ? Qgis::BrowserLayerType::Polygon :
144                       geometryType == QgsWkbTypes::LineGeometry ? Qgis::BrowserLayerType::Line
145                       : geometryType == QgsWkbTypes::PointGeometry ? Qgis::BrowserLayerType::Point : Qgis::BrowserLayerType::Vector );
146           break;
147 
148         case QgsArcGisRestQueryUtils::Raster:
149           layerItem = std::make_unique< QgsArcGisMapServiceLayerItem >( parent, name, url, id, name, authid, format, authcfg, headers );
150           static_cast< QgsArcGisMapServiceLayerItem * >( layerItem.get() )->setSupportedFormats( supportedFormats );
151           break;
152 
153         case QgsArcGisRestQueryUtils::AllTypes:
154           break;
155       }
156       if ( layerItem )
157         layerItems.insert( id, layerItem.release() );
158     }
159 
160   }, serviceData, parentUrl, supportedFormats, serviceTypeFilter );
161 
162   // create groups
163   for ( auto it = layerItems.constBegin(); it != layerItems.constEnd(); ++it )
164   {
165     const QString id = it.key();
166     QgsDataItem *item = it.value();
167     const QString parentId = parents.value( id );
168 
169     if ( QgsDataItem *layerParent = parentId.isEmpty() ? nullptr : layerItems.value( parentId ) )
170       layerParent->addChildItem( item );
171     else
172       items.append( item );
173   }
174 }
175 
176 //
177 // QgsArcGisRestConnectionItem
178 //
179 
QgsArcGisRestConnectionItem(QgsDataItem * parent,const QString & name,const QString & path,const QString & connectionName)180 QgsArcGisRestConnectionItem::QgsArcGisRestConnectionItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &connectionName )
181   : QgsDataCollectionItem( parent, name, path, QStringLiteral( "AFS" ) )
182   , mConnName( connectionName )
183 {
184   mIconName = QStringLiteral( "mIconConnect.svg" );
185   mCapabilities |= Qgis::BrowserItemCapability::Collapse;
186 
187   const QgsSettings settings;
188   const QString key = QStringLiteral( "qgis/connections-arcgisfeatureserver/" ) + mConnName;
189   mPortalContentEndpoint = settings.value( key + "/content_endpoint" ).toString();
190   mPortalCommunityEndpoint = settings.value( key + "/community_endpoint" ).toString();
191 }
192 
createChildren()193 QVector<QgsDataItem *> QgsArcGisRestConnectionItem::createChildren()
194 {
195   const QgsOwsConnection connection( QStringLiteral( "ARCGISFEATURESERVER" ), mConnName );
196   const QString url = connection.uri().param( QStringLiteral( "url" ) );
197   const QString authcfg = connection.uri().authConfigId();
198   const QString referer = connection.uri().param( QStringLiteral( "referer" ) );
199   QgsStringMap headers;
200   if ( ! referer.isEmpty() )
201     headers[ QStringLiteral( "Referer" )] = referer;
202 
203   QVector<QgsDataItem *> items;
204   if ( !mPortalCommunityEndpoint.isEmpty() && !mPortalContentEndpoint.isEmpty() )
205   {
206     items << new QgsArcGisPortalGroupsItem( this, QStringLiteral( "groups" ), authcfg, headers, mPortalCommunityEndpoint, mPortalContentEndpoint );
207     items << new QgsArcGisRestServicesItem( this, url, QStringLiteral( "services" ), authcfg, headers );
208   }
209   else
210   {
211     QString errorTitle, errorMessage;
212     const QVariantMap serviceData = QgsArcGisRestQueryUtils::getServiceInfo( url, authcfg, errorTitle, errorMessage, headers );
213     if ( serviceData.isEmpty() )
214     {
215       if ( !errorMessage.isEmpty() )
216       {
217         std::unique_ptr< QgsErrorItem > error = std::make_unique< QgsErrorItem >( this, tr( "Connection failed: %1" ).arg( errorTitle ), mPath + "/error" );
218         error->setToolTip( errorMessage );
219         items.append( error.release() );
220         QgsDebugMsg( "Connection failed - " + errorMessage );
221       }
222       return items;
223     }
224 
225     addFolderItems( items, serviceData, url, authcfg, headers, this, QString() );
226     addServiceItems( items, serviceData, url, authcfg, headers, this, QString() );
227     addLayerItems( items, serviceData, url, authcfg, headers, this, QgsArcGisRestQueryUtils::AllTypes, QString() );
228   }
229 
230   return items;
231 }
232 
equal(const QgsDataItem * other)233 bool QgsArcGisRestConnectionItem::equal( const QgsDataItem *other )
234 {
235   const QgsArcGisRestConnectionItem *o = qobject_cast<const QgsArcGisRestConnectionItem *>( other );
236   return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName );
237 }
238 
url() const239 QString QgsArcGisRestConnectionItem::url() const
240 {
241   const QgsOwsConnection connection( QStringLiteral( "ARCGISFEATURESERVER" ), mConnName );
242   return connection.uri().param( QStringLiteral( "url" ) );
243 }
244 
245 
246 //
247 // QgsArcGisPortalGroupsItem
248 //
249 
QgsArcGisPortalGroupsItem(QgsDataItem * parent,const QString & path,const QString & authcfg,const QgsStringMap & headers,const QString & communityEndpoint,const QString & contentEndpoint)250 QgsArcGisPortalGroupsItem::QgsArcGisPortalGroupsItem( QgsDataItem *parent, const QString &path, const QString &authcfg, const QgsStringMap &headers, const QString &communityEndpoint, const QString &contentEndpoint )
251   : QgsDataCollectionItem( parent, tr( "Groups" ), path, QStringLiteral( "AFS" ) )
252   , mAuthCfg( authcfg )
253   , mHeaders( headers )
254   , mPortalCommunityEndpoint( communityEndpoint )
255   , mPortalContentEndpoint( contentEndpoint )
256 {
257   mIconName = QStringLiteral( "mIconDbSchema.svg" );
258   mCapabilities |= Qgis::BrowserItemCapability::Collapse;
259   setToolTip( path );
260 }
261 
262 
createChildren()263 QVector<QgsDataItem *> QgsArcGisPortalGroupsItem::createChildren()
264 {
265   QVector<QgsDataItem *> items;
266 
267   QString errorTitle;
268   QString errorMessage;
269   const QVariantList groups = QgsArcGisPortalUtils::retrieveUserGroups( mPortalCommunityEndpoint, QString(), mAuthCfg, errorTitle, errorMessage, mHeaders );
270   if ( groups.isEmpty() )
271   {
272     if ( !errorMessage.isEmpty() )
273     {
274       std::unique_ptr< QgsErrorItem > error = std::make_unique< QgsErrorItem >( this, tr( "Connection failed: %1" ).arg( errorTitle ), mPath + "/error" );
275       error->setToolTip( errorMessage );
276       items.append( error.release() );
277       QgsDebugMsg( "Connection failed - " + errorMessage );
278     }
279     return items;
280   }
281 
282   for ( const QVariant &group : groups )
283   {
284     const QVariantMap groupData = group.toMap();
285     items << new QgsArcGisPortalGroupItem( this, groupData.value( QStringLiteral( "id" ) ).toString(),
286                                            groupData.value( QStringLiteral( "title" ) ).toString(),
287                                            mAuthCfg, mHeaders, mPortalCommunityEndpoint, mPortalContentEndpoint );
288     items.last()->setToolTip( groupData.value( QStringLiteral( "snippet" ) ).toString() );
289   }
290 
291   return items;
292 }
293 
equal(const QgsDataItem * other)294 bool QgsArcGisPortalGroupsItem::equal( const QgsDataItem *other )
295 {
296   const QgsArcGisPortalGroupsItem *o = qobject_cast<const QgsArcGisPortalGroupsItem *>( other );
297   return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName );
298 }
299 
300 
301 //
302 // QgsArcGisPortalGroupItem
303 //
QgsArcGisPortalGroupItem(QgsDataItem * parent,const QString & groupId,const QString & name,const QString & authcfg,const QgsStringMap & headers,const QString & communityEndpoint,const QString & contentEndpoint)304 QgsArcGisPortalGroupItem::QgsArcGisPortalGroupItem( QgsDataItem *parent, const QString &groupId, const QString &name, const QString &authcfg, const QgsStringMap &headers, const QString &communityEndpoint, const QString &contentEndpoint )
305   : QgsDataCollectionItem( parent, name, groupId, QStringLiteral( "AFS" ) )
306   , mId( groupId )
307   , mAuthCfg( authcfg )
308   , mHeaders( headers )
309   , mPortalCommunityEndpoint( communityEndpoint )
310   , mPortalContentEndpoint( contentEndpoint )
311 {
312   mIconName = QStringLiteral( "mIconDbSchema.svg" );
313   mCapabilities |= Qgis::BrowserItemCapability::Collapse;
314   setToolTip( name );
315 }
316 
createChildren()317 QVector<QgsDataItem *> QgsArcGisPortalGroupItem::createChildren()
318 {
319   QVector<QgsDataItem *> items;
320 
321   QString errorTitle;
322   QString errorMessage;
323   const QVariantList groupItems = QgsArcGisPortalUtils::retrieveGroupItemsOfType( mPortalContentEndpoint, mId, mAuthCfg, QList<int >() << QgsArcGisPortalUtils::FeatureService
324                                   << QgsArcGisPortalUtils::MapService
325                                   << QgsArcGisPortalUtils::ImageService,
326                                   errorTitle, errorMessage, mHeaders );
327   if ( groupItems.isEmpty() )
328   {
329     if ( !errorMessage.isEmpty() )
330     {
331       std::unique_ptr< QgsErrorItem > error = std::make_unique< QgsErrorItem >( this, tr( "Connection failed: %1" ).arg( errorTitle ), mPath + "/error" );
332       error->setToolTip( errorMessage );
333       items.append( error.release() );
334       QgsDebugMsg( "Connection failed - " + errorMessage );
335     }
336     return items;
337   }
338 
339   for ( const QVariant &item : groupItems )
340   {
341     const QVariantMap itemData = item.toMap();
342 
343     if ( itemData.value( QStringLiteral( "type" ) ).toString().compare( QStringLiteral( "Feature Service" ), Qt::CaseInsensitive ) == 0 )
344     {
345       items << new QgsArcGisFeatureServiceItem( this, itemData.value( QStringLiteral( "title" ) ).toString(),
346             itemData.value( QStringLiteral( "url" ) ).toString(),
347             itemData.value( QStringLiteral( "url" ) ).toString(), mAuthCfg, mHeaders );
348     }
349     else
350     {
351       items << new QgsArcGisMapServiceItem( this, itemData.value( QStringLiteral( "title" ) ).toString(),
352                                             itemData.value( QStringLiteral( "url" ) ).toString(),
353                                             itemData.value( QStringLiteral( "url" ) ).toString(), mAuthCfg, mHeaders, itemData.value( QStringLiteral( "type" ) ).toString().compare( QStringLiteral( "Map Service" ), Qt::CaseInsensitive ) == 0 ? QStringLiteral( "MapServer" ) : QStringLiteral( "ImageServer" ) );
354     }
355   }
356 
357   return items;
358 }
359 
equal(const QgsDataItem * other)360 bool QgsArcGisPortalGroupItem::equal( const QgsDataItem *other )
361 {
362   const QgsArcGisPortalGroupItem *o = qobject_cast<const QgsArcGisPortalGroupItem *>( other );
363   return ( type() == other->type() && o && mId == o->mId && mName == o->mName );
364 }
365 
366 
367 //
368 // QgsArcGisRestServicesItem
369 //
370 
QgsArcGisRestServicesItem(QgsDataItem * parent,const QString & url,const QString & path,const QString & authcfg,const QgsStringMap & headers)371 QgsArcGisRestServicesItem::QgsArcGisRestServicesItem( QgsDataItem *parent, const QString &url, const QString &path, const QString &authcfg, const QgsStringMap &headers )
372   : QgsDataCollectionItem( parent, tr( "Services" ), path, QStringLiteral( "AFS" ) )
373   , mUrl( url )
374   , mAuthCfg( authcfg )
375   , mHeaders( headers )
376 {
377   mIconName = QStringLiteral( "mIconDbSchema.svg" );
378   mCapabilities |= Qgis::BrowserItemCapability::Collapse;
379 }
380 
createChildren()381 QVector<QgsDataItem *> QgsArcGisRestServicesItem::createChildren()
382 {
383   QVector<QgsDataItem *> items;
384   QString errorTitle, errorMessage;
385   const QVariantMap serviceData = QgsArcGisRestQueryUtils::getServiceInfo( mUrl, mAuthCfg, errorTitle, errorMessage, mHeaders );
386   if ( serviceData.isEmpty() )
387   {
388     if ( !errorMessage.isEmpty() )
389     {
390       std::unique_ptr< QgsErrorItem > error = std::make_unique< QgsErrorItem >( this, tr( "Connection failed: %1" ).arg( errorTitle ), mPath + "/error" );
391       error->setToolTip( errorMessage );
392       items.append( error.release() );
393       QgsDebugMsg( "Connection failed - " + errorMessage );
394     }
395     return items;
396   }
397 
398   addFolderItems( items, serviceData, mUrl, mAuthCfg, mHeaders, this, QString() );
399   addServiceItems( items, serviceData, mUrl, mAuthCfg, mHeaders, this, QString() );
400   addLayerItems( items, serviceData, mUrl, mAuthCfg, mHeaders, this, QgsArcGisRestQueryUtils::AllTypes, QString() );
401   return items;
402 }
403 
equal(const QgsDataItem * other)404 bool QgsArcGisRestServicesItem::equal( const QgsDataItem *other )
405 {
406   const QgsArcGisRestServicesItem *o = qobject_cast<const QgsArcGisRestServicesItem *>( other );
407   return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName );
408 }
409 
410 
411 
412 //
413 // QgsArcGisRestFolderItem
414 //
QgsArcGisRestFolderItem(QgsDataItem * parent,const QString & name,const QString & path,const QString & baseUrl,const QString & authcfg,const QgsStringMap & headers)415 QgsArcGisRestFolderItem::QgsArcGisRestFolderItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers )
416   : QgsDataCollectionItem( parent, name, path, QStringLiteral( "AFS" ) )
417   , mBaseUrl( baseUrl )
418   , mAuthCfg( authcfg )
419   , mHeaders( headers )
420 {
421   mIconName = QStringLiteral( "mIconDbSchema.svg" );
422   mCapabilities |= Qgis::BrowserItemCapability::Collapse;
423   setToolTip( path );
424 }
425 
setSupportedFormats(const QString & formats)426 void QgsArcGisRestFolderItem::setSupportedFormats( const QString &formats )
427 {
428   mSupportedFormats = formats;
429 }
430 
431 
createChildren()432 QVector<QgsDataItem *> QgsArcGisRestFolderItem::createChildren()
433 {
434   const QString url = mPath;
435 
436   QVector<QgsDataItem *> items;
437   QString errorTitle, errorMessage;
438   const QVariantMap serviceData = QgsArcGisRestQueryUtils::getServiceInfo( url, mAuthCfg, errorTitle, errorMessage, mHeaders );
439   if ( serviceData.isEmpty() )
440   {
441     if ( !errorMessage.isEmpty() )
442     {
443       std::unique_ptr< QgsErrorItem > error = std::make_unique< QgsErrorItem >( this, tr( "Connection failed: %1" ).arg( errorTitle ), mPath + "/error" );
444       error->setToolTip( errorMessage );
445       items.append( error.release() );
446       QgsDebugMsg( "Connection failed - " + errorMessage );
447     }
448     return items;
449   }
450 
451   addFolderItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this, mSupportedFormats );
452   addServiceItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this, mSupportedFormats );
453   addLayerItems( items, serviceData, mPath, mAuthCfg, mHeaders, this, QgsArcGisRestQueryUtils::Vector, mSupportedFormats );
454   return items;
455 }
456 
equal(const QgsDataItem * other)457 bool QgsArcGisRestFolderItem::equal( const QgsDataItem *other )
458 {
459   const QgsArcGisRestFolderItem *o = qobject_cast<const QgsArcGisRestFolderItem *>( other );
460   return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName );
461 }
462 
463 //
464 // QgsArcGisFeatureServiceItem
465 //
QgsArcGisFeatureServiceItem(QgsDataItem * parent,const QString & name,const QString & path,const QString & baseUrl,const QString & authcfg,const QgsStringMap & headers)466 QgsArcGisFeatureServiceItem::QgsArcGisFeatureServiceItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers )
467   : QgsDataCollectionItem( parent, name, path, QStringLiteral( "AFS" ) )
468   , mBaseUrl( baseUrl )
469   , mAuthCfg( authcfg )
470   , mHeaders( headers )
471 {
472   mIconName = QStringLiteral( "mIconDbSchema.svg" );
473   mCapabilities |= Qgis::BrowserItemCapability::Collapse;
474   setToolTip( path );
475 }
476 
setSupportedFormats(const QString & formats)477 void QgsArcGisFeatureServiceItem::setSupportedFormats( const QString &formats )
478 {
479   mSupportedFormats = formats;
480 }
481 
createChildren()482 QVector<QgsDataItem *> QgsArcGisFeatureServiceItem::createChildren()
483 {
484   const QString url = mPath;
485 
486   QVector<QgsDataItem *> items;
487   QString errorTitle, errorMessage;
488   const QVariantMap serviceData = QgsArcGisRestQueryUtils::getServiceInfo( url, mAuthCfg, errorTitle, errorMessage, mHeaders );
489   if ( serviceData.isEmpty() )
490   {
491     if ( !errorMessage.isEmpty() )
492     {
493       std::unique_ptr< QgsErrorItem > error = std::make_unique< QgsErrorItem >( this, tr( "Connection failed: %1" ).arg( errorTitle ), mPath + "/error" );
494       error->setToolTip( errorMessage );
495       items.append( error.release() );
496       QgsDebugMsg( "Connection failed - " + errorMessage );
497     }
498     return items;
499   }
500 
501   addFolderItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this, mSupportedFormats );
502   addServiceItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this, mSupportedFormats );
503   addLayerItems( items, serviceData, mPath, mAuthCfg, mHeaders, this, QgsArcGisRestQueryUtils::Vector, mSupportedFormats );
504   return items;
505 }
506 
equal(const QgsDataItem * other)507 bool QgsArcGisFeatureServiceItem::equal( const QgsDataItem *other )
508 {
509   const QgsArcGisFeatureServiceItem *o = qobject_cast<const QgsArcGisFeatureServiceItem *>( other );
510   return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName );
511 }
512 
513 
514 //
515 // QgsArcGisMapServiceItem
516 //
517 
QgsArcGisMapServiceItem(QgsDataItem * parent,const QString & name,const QString & path,const QString & baseUrl,const QString & authcfg,const QgsStringMap & headers,const QString & serviceType)518 QgsArcGisMapServiceItem::QgsArcGisMapServiceItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers, const QString &serviceType )
519   : QgsDataCollectionItem( parent, name, path, QStringLiteral( "AMS" ) )
520   , mBaseUrl( baseUrl )
521   , mAuthCfg( authcfg )
522   , mHeaders( headers )
523   , mServiceType( serviceType )
524 {
525   mIconName = QStringLiteral( "mIconDbSchema.svg" );
526   mCapabilities |= Qgis::BrowserItemCapability::Collapse;
527   setToolTip( path );
528 }
529 
createChildren()530 QVector<QgsDataItem *> QgsArcGisMapServiceItem::createChildren()
531 {
532   const QString url = mPath;
533 
534   QVector<QgsDataItem *> items;
535   QString errorTitle, errorMessage;
536   const QVariantMap serviceData = QgsArcGisRestQueryUtils::getServiceInfo( url, mAuthCfg, errorTitle, errorMessage, mHeaders );
537   if ( serviceData.isEmpty() )
538   {
539     if ( !errorMessage.isEmpty() )
540     {
541       std::unique_ptr< QgsErrorItem > error = std::make_unique< QgsErrorItem >( this, tr( "Connection failed: %1" ).arg( errorTitle ), mPath + "/error" );
542       error->setToolTip( errorMessage );
543       items.append( error.release() );
544       QgsDebugMsg( "Connection failed - " + errorMessage );
545     }
546     return items;
547   }
548 
549   const QString supportedFormats = mServiceType == QLatin1String( "ImageServer" ) ?
550                                    QStringLiteral( "JPGPNG,PNG,PNG8,PNG24,JPG,BMP,GIF,TIFF,PNG32,BIP,BSQ,LERC" ) // ImageServer supported formats
551                                    : serviceData.value( QStringLiteral( "supportedImageFormatTypes" ) ).toString();
552 
553   addFolderItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this, supportedFormats );
554   addServiceItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this, supportedFormats );
555   addLayerItems( items, serviceData, mPath, mAuthCfg, mHeaders, this, QgsArcGisRestQueryUtils::AllTypes, supportedFormats );
556   return items;
557 }
558 
equal(const QgsDataItem * other)559 bool QgsArcGisMapServiceItem::equal( const QgsDataItem *other )
560 {
561   const QgsArcGisMapServiceItem *o = qobject_cast<const QgsArcGisMapServiceItem *>( other );
562   return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName );
563 }
564 
565 
566 //
567 // QgsArcGisFeatureServiceLayerItem
568 //
569 
QgsArcGisFeatureServiceLayerItem(QgsDataItem * parent,const QString &,const QString & url,const QString & title,const QString & authid,const QString & authcfg,const QgsStringMap & headers,Qgis::BrowserLayerType geometryType)570 QgsArcGisFeatureServiceLayerItem::QgsArcGisFeatureServiceLayerItem( QgsDataItem *parent, const QString &, const QString &url, const QString &title, const QString &authid, const QString &authcfg, const QgsStringMap &headers, Qgis::BrowserLayerType geometryType )
571   : QgsLayerItem( parent, title, url, QString(), geometryType, QStringLiteral( "arcgisfeatureserver" ) )
572 {
573   mUri = QStringLiteral( "crs='%1' url='%2'" ).arg( authid, url );
574   if ( !authcfg.isEmpty() )
575     mUri += QStringLiteral( " authcfg='%1'" ).arg( authcfg );
576   if ( !headers.value( QStringLiteral( "Referer" ) ).isEmpty() )
577     mUri += QStringLiteral( " referer='%1'" ).arg( headers.value( QStringLiteral( "Referer" ) ) );
578   setState( Qgis::BrowserItemState::Populated );
579   setToolTip( url );
580 }
581 
582 //
583 // QgsArcGisMapServiceLayerItem
584 //
585 
QgsArcGisMapServiceLayerItem(QgsDataItem * parent,const QString &,const QString & url,const QString & id,const QString & title,const QString & authid,const QString & format,const QString & authcfg,const QgsStringMap & headers)586 QgsArcGisMapServiceLayerItem::QgsArcGisMapServiceLayerItem( QgsDataItem *parent, const QString &, const QString &url, const QString &id, const QString &title, const QString &authid, const QString &format, const QString &authcfg, const QgsStringMap &headers )
587   : QgsLayerItem( parent, title, url, QString(), Qgis::BrowserLayerType::Raster, QStringLiteral( "arcgismapserver" ) )
588 {
589   const QString trimmedUrl = id.isEmpty() ? url : url.left( url.length() - 1 - id.length() ); // trim '/0' from end of url -- AMS provider requires this omitted
590   mUri = QStringLiteral( "crs='%1' format='%2' layer='%3' url='%4'" ).arg( authid, format, id, trimmedUrl );
591   if ( !authcfg.isEmpty() )
592     mUri += QStringLiteral( " authcfg='%1'" ).arg( authcfg );
593   if ( !headers.value( QStringLiteral( "Referer" ) ).isEmpty() )
594     mUri += QStringLiteral( " referer='%1'" ).arg( headers.value( QStringLiteral( "Referer" ) ) );
595   setState( Qgis::BrowserItemState::Populated );
596   setToolTip( mPath );
597 }
598 
599 //
600 // QgsArcGisRestParentLayerItem
601 //
602 
QgsArcGisRestParentLayerItem(QgsDataItem * parent,const QString & name,const QString & path,const QString & authcfg,const QgsStringMap & headers)603 QgsArcGisRestParentLayerItem::QgsArcGisRestParentLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &authcfg, const QgsStringMap &headers )
604   : QgsDataItem( Qgis::BrowserItemType::Collection, parent, name, path )
605   , mAuthCfg( authcfg )
606   , mHeaders( headers )
607 {
608   mCapabilities |= Qgis::BrowserItemCapability::Fast;
609   mIconName = QStringLiteral( "mIconDbSchema.svg" );
610   setToolTip( path );
611 }
612 
equal(const QgsDataItem * other)613 bool QgsArcGisRestParentLayerItem::equal( const QgsDataItem *other )
614 {
615   const QgsArcGisRestParentLayerItem *o = qobject_cast<const QgsArcGisRestParentLayerItem *>( other );
616   return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName );
617 }
618 
619 
620 //
621 // QgsArcGisRestDataItemProvider
622 //
623 
QgsArcGisRestDataItemProvider()624 QgsArcGisRestDataItemProvider::QgsArcGisRestDataItemProvider()
625 {
626   // migrate legacy map services by moving them to feature server group
627 
628   QgsSettings settings;
629   settings.beginGroup( QStringLiteral( "qgis/connections-arcgismapserver" ) );
630   const QStringList legacyServices = settings.childGroups();
631   settings.endGroup();
632   settings.beginGroup( QStringLiteral( "qgis/connections-arcgisfeatureserver" ) );
633   QStringList existingServices = settings.childGroups();
634   settings.endGroup();
635   for ( const QString &legacyService : legacyServices )
636   {
637     QString newName = legacyService;
638     int i = 1;
639     while ( existingServices.contains( newName ) )
640     {
641       i ++;
642       newName = QStringLiteral( "%1 (%2)" ).arg( legacyService ).arg( i );
643     }
644 
645     settings.beginGroup( QStringLiteral( "qgis/connections-arcgismapserver/%1" ).arg( legacyService ) );
646     const QStringList keys = settings.childKeys();
647     settings.endGroup();
648     for ( const QString &key : keys )
649     {
650       const QString oldKey = QStringLiteral( "qgis/connections-arcgismapserver/%1/%2" ).arg( legacyService, key );
651       const QString newKey = QStringLiteral( "qgis/connections-arcgisfeatureserver/%1/%2" ).arg( newName, key );
652       settings.setValue( newKey, settings.value( oldKey ) );
653     }
654 
655     settings.remove( QStringLiteral( "qgis/connections-arcgismapserver/%1" ).arg( legacyService ) );
656     existingServices.append( newName );
657   }
658 }
659 
name()660 QString QgsArcGisRestDataItemProvider::name()
661 {
662   return QStringLiteral( "AFS" );
663 }
664 
capabilities() const665 int QgsArcGisRestDataItemProvider::capabilities() const
666 {
667   return QgsDataProvider::Net;
668 }
669 
createDataItem(const QString & path,QgsDataItem * parentItem)670 QgsDataItem *QgsArcGisRestDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem )
671 {
672   if ( path.isEmpty() )
673   {
674     return new QgsArcGisRestRootItem( parentItem, QObject::tr( "ArcGIS REST Servers" ), QStringLiteral( "arcgisfeatureserver:" ) );
675   }
676 
677   // path schema: afs:/connection name (used by OWS)
678   if ( path.startsWith( QLatin1String( "afs:/" ) ) )
679   {
680     const QString connectionName = path.split( '/' ).last();
681     if ( QgsOwsConnection::connectionList( QStringLiteral( "arcgisfeatureserver" ) ).contains( connectionName ) )
682     {
683       return new QgsArcGisRestConnectionItem( parentItem, QStringLiteral( "ArcGisFeatureServer" ), path, connectionName );
684     }
685   }
686 
687   return nullptr;
688 }
689 
690