Skip to content

Commit 03a0aaf

Browse files
authored
Merge pull request #49 from geo2france/dev
Merge dev 0.2.2
2 parents a3f5ebf + 42bd5e3 commit 03a0aaf

File tree

8 files changed

+175
-92
lines changed

8 files changed

+175
-92
lines changed

plugin/idg/gui/dlg_settings.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,9 @@ def apply(self):
135135
# dump new settings into QgsSettings
136136
self.plg_settings.save_from_object(settings) #Les variables globales ne sont peut être pas MAJ ici
137137

138-
iface.mainWindow().findChildren(QWidget, 'Browser')[0].refresh() # refresh browser (supprimer et recreer le registre IDG plutôt ?)
139-
138+
registry = QgsApplication.dataItemProviderRegistry()
139+
provider = registry.provider('IDG Provider')
140+
provider.root.repopulate()
140141
if __debug__:
141142
self.log(
142143
message="DEBUG - Settings successfully saved.",

plugin/idg/metadata.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ description[fr]=Plugin fournissant un accès simple aux données de différentes
77
about=Plugin providing easy access to data from different Spatial Data Infrastructures
88
about[fr]=Plugin fournissant un accès simple aux données de différentes Infrastructures de Données Géographiques DataGrandEst, Géo2France, GeoBretagne et OPenIG
99
icon=resources/images/layers-svgrepo-com.svg
10-
tags=opendata,sdi,DataGrandEst,Géo2France,GéoBretagne,OPenIG
11-
tags[fr]=opendata,idg,DataGrandEst,Géo2France,GéoBretagne,OPenIG
10+
tags=opendata,sdi,DataGrandEst,Géo2France,GéoBretagne,OPenIG, crige
11+
tags[fr]=opendata,idg,DataGrandEst,Géo2France,GéoBretagne,OPenIG,crige
1212

1313
# credits and contact
1414
author=Benjamin CHARTIER, Jean-Baptiste DESBAS
@@ -24,5 +24,5 @@ qgisMinimumVersion=3.00
2424
qgisMaximumVersion=3.99
2525

2626
# versioning
27-
version=0.2.1
28-
changelog=
27+
version=0.2.2
28+
changelog=Amélioration performance et stabilité

plugin/idg/plugin_main.py

+20-14
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from idg.gui.dock import DockWidget
2424
from idg.gui.about_box import AboutBox
2525
from idg.gui.param_box import ParamBox
26-
from idg.toolbelt.tree_node_factory import TreeNodeFactory, download_tree_config_file, download_all_config_files, download_default_idg_list
26+
from idg.toolbelt.tree_node_factory import TreeNodeFactory, download_tree_config_file, download_all_config_files, download_default_idg_list, DownloadAllConfigFilesAsync
2727

2828
import os
2929
import json
@@ -57,15 +57,19 @@ def __init__(self, iface: QgisInterface):
5757

5858
config_struct = None
5959
config_string = ""
60-
61-
# Download the config if needed
62-
#if self.need_download_tree_config_file():
63-
# download_tree_config_file(PluginGlobals.instance().CONFIG_FILE_URLS[0])
64-
65-
# Read the resources tree file and update the GUI
66-
#self.ressources_tree = TreeNodeFactory(PluginGlobals.instance().config_file_path).root_node # dev
67-
download_default_idg_list()
68-
download_all_config_files(RemotePlatforms().stock_idgs)
60+
61+
self.registry = QgsApplication.dataItemProviderRegistry()
62+
self.provider = IdgProvider(self.iface)
63+
64+
self.iface.initializationCompleted.connect(self.post_ui_init)
65+
66+
67+
def post_ui_init(self):
68+
"""Run after plugin's UI has been initialized."""
69+
download_default_idg_list() # TODO a passer en asynchrone aussi ?
70+
self.task = DownloadAllConfigFilesAsync(RemotePlatforms().stock_idgs)
71+
self.task.finished.connect(self.populate_browser)
72+
self.task.start()
6973

7074
def need_download_tree_config_file(self):
7175
"""
@@ -112,15 +116,17 @@ def initGui(self):
112116
# Create a menu
113117
self.createPluginMenu()
114118

119+
# Add browser IDG provider
120+
self.registry.addProvider(self.provider)
121+
115122
# Create a dockable panel with a tree of resources
116123
#self.dock = DockWidget()
117124
#self.dock.set_tree_content(self.ressources_tree)
118125
#self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dock) # dev
119126

120-
# Add browser provider
121-
self.registry = QgsApplication.dataItemProviderRegistry()
122-
self.provider = IdgProvider(self.iface)
123-
self.registry.addProvider(self.provider)
127+
128+
def populate_browser(self):
129+
self.provider.root.repopulate()
124130

125131
def unload(self):
126132
"""Cleans up when plugin is disabled/uninstalled."""

plugin/idg/toolbelt/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
from .singleton import Singleton
88
from .browser import IdgProvider
99
from .remote_platforms import RemotePlatforms
10+
from .network_manager import NetworkRequestsManager # noqa: F401

plugin/idg/toolbelt/browser.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from qgis.core import QgsDataItemProvider, QgsDataCollectionItem, QgsDataItem, QgsDataProvider, QgsProject, \
1+
from qgis.core import Qgis, QgsDataItemProvider, QgsDataCollectionItem, QgsDataItem, QgsDataProvider, QgsProject, \
22
QgsLayerTreeLayer, QgsLayerTreeGroup, QgsMimeDataUtils, QgsAbstractMetadataBase, QgsApplication, QgsIconUtils
33
from qgis.gui import QgisInterface
44
from qgis.PyQt.QtGui import QIcon
@@ -8,9 +8,7 @@
88
from idg.__about__ import __title__
99
from qgis.PyQt.QtWidgets import QAction, QMenu
1010
from qgis.utils import iface
11-
1211
import os.path
13-
import json
1412
import webbrowser
1513

1614

@@ -40,15 +38,16 @@ def capabilities(self):
4038
return QgsDataProvider.Net
4139

4240
def createDataItem(self, path, parentItem):
43-
root = RootCollection(self.iface)
44-
return root
41+
self.root = RootCollection(self.iface)
42+
return self.root
4543

4644

4745
class RootCollection(QgsDataCollectionItem):
4846
def __init__(self, iface: QgisInterface):
4947
self.iface = iface
5048
QgsDataCollectionItem.__init__(self, None, "IDG", "/IDG")
5149
self.setIcon(QIcon(PluginGlobals.instance().plugin_path+'/resources/images/layers-svgrepo-com.svg'))
50+
self.setState(Qgis.BrowserItemState.Populating)
5251

5352
def actions(self, parent):
5453
actions = list()
@@ -71,15 +70,15 @@ def menus(self, parent):
7170
menu.addSeparator()
7271
menu.addAction(QAction(self.tr('Add URL'), menu, )) # TODO Liens vers le panneau Options de QGIS
7372
return [menu]
74-
75-
def createChildren(self):
76-
children = []
73+
74+
def repopulate(self):
75+
self.refresh()
7776
for pf in RemotePlatforms().plateforms :
7877
if pf.is_hidden() :
7978
continue
8079
pf_collection = PlatformCollection(plateform=pf)
81-
children.append(pf_collection)
82-
return children
80+
self.addChildItem(pf_collection, refresh=True)
81+
self.setState(Qgis.BrowserItemState.Populated)
8382

8483

8584
class PlatformCollection(QgsDataCollectionItem):
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#! python3 # noqa: E265
2+
3+
"""
4+
Perform network request.
5+
(from https://github.com/geotribu/qtribu/blob/main/qtribu/toolbelt/network_manager.py)
6+
"""
7+
8+
# ############################################################################
9+
# ########## Imports ###############
10+
# ##################################
11+
12+
# Standard library
13+
import logging
14+
from os import remove, path
15+
from shutil import copy
16+
# PyQGIS
17+
from qgis.core import QgsBlockingNetworkRequest, QgsFileDownloader
18+
from qgis.PyQt.QtCore import QByteArray, QCoreApplication, QEventLoop, QUrl
19+
20+
# project
21+
from idg.toolbelt.log_handler import PlgLogger
22+
23+
24+
# ############################################################################
25+
# ########## Globals ###############
26+
# ##################################
27+
28+
logger = logging.getLogger(__name__)
29+
30+
# ############################################################################
31+
# ########## Classes ###############
32+
# ##################################
33+
34+
35+
class NetworkRequestsManager:
36+
"""Helper on network operations.
37+
38+
:param tr: method to translate
39+
:type tr: func
40+
"""
41+
42+
def __init__(self):
43+
"""Initialization."""
44+
self.log = PlgLogger().log
45+
self.ntwk_requester = QgsBlockingNetworkRequest()
46+
47+
def tr(self, message: str) -> str:
48+
"""Get the translation for a string using Qt translation API.
49+
50+
:param message: string to be translated.
51+
:type message: str
52+
53+
:returns: Translated version of message.
54+
:rtype: str
55+
"""
56+
return QCoreApplication.translate(self.__class__.__name__, message)
57+
58+
59+
def download_file(self, remote_url: str, local_path: str) -> str:
60+
"""Download a file from a remote web server accessible through HTTP.
61+
62+
:param remote_url: remote URL
63+
:type remote_url: str
64+
:param local_path: path to the local file
65+
:type local_path: str
66+
:return: output path
67+
:rtype: str
68+
"""
69+
70+
def dlCompleted():
71+
self.log(message=f"Download of {remote_url} to {local_path} succeedeed", log_level=3)
72+
copy(local_path+'_tmp', local_path)
73+
remove(local_path+'_tmp')
74+
75+
self.log(
76+
message=f"Downloading file from {remote_url} to {local_path}", log_level=4
77+
)
78+
# download it
79+
loop = QEventLoop()
80+
file_downloader = QgsFileDownloader(
81+
url = QUrl(remote_url ), outputFileName=local_path+'_tmp', delayStart=True # Le téléchargement se fait dans un fichier temporaire, pour garder l'ancien fichier en cas d'échec
82+
)
83+
file_downloader.downloadCompleted.connect(dlCompleted)
84+
file_downloader.downloadError.connect(
85+
lambda e : self.log(message=f"Download of {remote_url} to {local_path} error {e}", log_level=1)
86+
)
87+
file_downloader.downloadExited.connect(loop.quit)
88+
file_downloader.startDownload()
89+
loop.exec_()
90+
91+
92+
return local_path

plugin/idg/toolbelt/tree_node_factory.py

+46-39
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
QgsProject,
1212
QgsNetworkAccessManager,
1313
QgsNetworkReplyContent,
14+
QgsFileDownloader
1415
)
1516
from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply
16-
from qgis.PyQt.QtCore import QUrl
17+
from qgis.PyQt.QtCore import QUrl, QThread, pyqtSignal
1718

1819
from idg.toolbelt import PluginGlobals
20+
from .network_manager import NetworkRequestsManager
1921
from .nodes import WmsLayerTreeNode, WmsStyleLayerTreeNode, WmtsLayerTreeNode, WfsFeatureTypeTreeNode
2022
from .nodes import WfsFeatureTypeFilterTreeNode, GdalWmsConfigFileTreeNode, FolderTreeNode
2123

@@ -28,14 +30,16 @@ def download_default_idg_list(url='https://raw.githubusercontent.com/geo2france/
2830
response: QgsNetworkReplyContent = manager.blockingGet(
2931
request, forceRefresh=True
3032
)
31-
if response.error() == QNetworkReply.NoError:
32-
try:
33-
os.remove(local_file)
34-
except OSError:
35-
pass
36-
with open(local_file, "wb") as local_config_file:
37-
local_config_file.write(response.content())
38-
return json.loads(bytes(response.content()).decode())
33+
qntwk = NetworkRequestsManager()
34+
local_file_name = qntwk.download_file(url, os.path.join(PluginGlobals.instance().config_dir_path, 'default_idg.json'))
35+
if local_file_name is not None:
36+
#try:
37+
# os.remove(local_file)
38+
#except OSError:
39+
# pass
40+
with open(local_file, "r") as local_config_file:
41+
out = json.load(local_config_file)
42+
return out
3943
#TOD gérer les erreur (garder le fichier précédent + avertissement)
4044

4145
def download_all_config_files(idgs): #remplacer la list par un dict ({idg_id:url})
@@ -47,52 +51,28 @@ def download_all_config_files(idgs): #remplacer la list par un dict ({idg_id:url
4751
key = IDG_id, value = url
4852
rename local file
4953
"""
54+
#TODO a passer dans RemotePlatforms
55+
qntwk = NetworkRequestsManager()
5056
for idg_id, url in idgs.items():
57+
#continue si l'IDG est masquée
5158
idg_id = str(idg_id)
5259
request = QNetworkRequest(QUrl(url))
5360
manager = QgsNetworkAccessManager.instance()
5461
response: QgsNetworkReplyContent = manager.blockingGet(
5562
request, forceRefresh=True
5663
)
5764
suffix = os.path.splitext(os.path.basename(url))[-1]
58-
local_file_name = os.path.join(PluginGlobals.instance().config_dir_path, idg_id + suffix)
59-
if response.error() == QNetworkReply.NoError:
60-
# Creer le dossier si non existant
61-
try:
62-
os.makedirs(os.path.join(PluginGlobals.instance().config_dir_path))
63-
except OSError:
64-
if not os.path.isdir(os.path.join(PluginGlobals.instance().config_dir_path)):
65-
raise
66-
# Supprimer le fichier si existant
67-
try :
68-
os.remove(os.path.join(PluginGlobals.instance().config_dir_path, idg_id + '.qgz') )
69-
except OSError:
70-
pass
71-
try :
72-
os.remove(os.path.join(PluginGlobals.instance().config_dir_path, idg_id + '.qgs') )
73-
except OSError:
74-
pass
65+
local_file_name = qntwk.download_file(url, os.path.join(PluginGlobals.instance().config_dir_path, idg_id + suffix))
66+
if local_file_name :
7567
with open(local_file_name, "wb") as local_config_file:
7668
local_config_file.write(response.content())
7769
# Download icon if custom TODO a factoriser
7870
project = QgsProject()
7971
project.read(local_file_name, QgsProject.ReadFlags()|QgsProject.FlagDontResolveLayers|QgsProject.FlagDontLoadLayouts)
8072
for l in project.metadata().links():
8173
if l.name.lower().strip() == 'icon':
82-
request = QNetworkRequest(QUrl(l.url))
83-
manager = QgsNetworkAccessManager.instance()
8474
suffix = os.path.splitext(os.path.basename(l.url))[-1]
85-
response: QgsNetworkReplyContent = manager.blockingGet(
86-
request, forceRefresh=True
87-
)
88-
if response.error() == QNetworkReply.NoError:
89-
local_icon_file_name = os.path.join(PluginGlobals.instance().config_dir_path, idg_id + suffix) #TODO : vérifier qu'il s'agit d'un type image
90-
try:
91-
os.remove(local_icon_file_name)
92-
except OSError:
93-
pass
94-
with open(local_icon_file_name, "wb") as icon_file:
95-
icon_file.write(response.content())
75+
qntwk.download_file(l.url, os.path.join(PluginGlobals.instance().config_dir_path, idg_id + suffix) )
9676
break
9777

9878
else :
@@ -101,6 +81,33 @@ def download_all_config_files(idgs): #remplacer la list par un dict ({idg_id:url
10181
"Erreur", short_message, level=Qgis.Warning
10282
)
10383

84+
class DownloadAllConfigFilesAsync(QThread):
85+
finished = pyqtSignal()
86+
def __init__(self, idgs):
87+
super(QThread, self).__init__()
88+
self.idgs=idgs
89+
def run(self):
90+
qntwk = NetworkRequestsManager()
91+
92+
for idg_id, url in self.idgs.items():
93+
# continue si l'IDG est masquée
94+
idg_id = str(idg_id)
95+
suffix = os.path.splitext(os.path.basename(url))[-1]
96+
local_file_name = qntwk.download_file(url, os.path.join(PluginGlobals.instance().config_dir_path,
97+
idg_id + suffix))
98+
if local_file_name:
99+
project = QgsProject()
100+
project.read(local_file_name,
101+
QgsProject.ReadFlags() | QgsProject.FlagDontResolveLayers | QgsProject.FlagDontLoadLayouts)
102+
for l in project.metadata().links():
103+
if l.name.lower().strip() == 'icon':
104+
suffix = os.path.splitext(os.path.basename(l.url))[-1]
105+
qntwk.download_file(l.url,
106+
os.path.join(PluginGlobals.instance().config_dir_path, idg_id + suffix))
107+
break
108+
self.finished.emit()
109+
110+
104111
def download_tree_config_file(file_url):
105112
"""
106113
Download the resources tree file

0 commit comments

Comments
 (0)