[QGIS-Developer] Difficulties with custom bad layer handler in plugin code

Áron Gergely aron.gergely at rasterra.nl
Mon Jul 22 07:57:58 PDT 2019



On 22/07/2019 01:11, Nyall Dawson wrote:
> On Sun, 21 Jul 2019 at 21:16, Áron Gergely<aron.gergely at rasterra.nl>  wrote:
>
>> This actually works, for the 1st time when I open a project file with bad layers. Then the 2nd time, QGIS always crashes.
>> It seems once I replaced the bad layer handler during project start, QGIS is not able to open a subsequent new project properly.
> Couldn't you achieve the same result by setting your bad layer handler
> directly in initGui?
No, for some strange reason the bad layer handler would not get assigned 
when I tried within initGui.
I tried to find out why by looking at debug messages on qgis launch, 
reading the source code,
but it is a lot to take in with my rusty C skills so I have not found 
the answer.

But I reckoned it may be due to the plugin being loaded before a 
QgsProject singleton would be initialized.
>> # TODO: after this, call the GUI file dialog as in the default bad layer handler, to allow the user to fix
>> # any other broken layers... not sure how to do this
> The bad news is -- unfortunately you cannot. The inbuilt bad layer
> handler isn't exposed to Python in any way, and as soon as you set a
> new bad layer
> handler method the existing inbuilt one is completely deleted.
Understood - if that part is not exposed to the python API then I will 
not pursue the above. Thanks for letting me know!
> I've been thinking about a similar situation recently, where a
> client's IT department is continually replacing network paths and
> moving things around (they do it roughly every 6 months, with no
> notice in advance. yep.) This breaks many many paths in their qgis
> projects, but the fix is actually super simple since we know what the
> network path used to be and what it is now. So what I've been
> pondering is whether we need a new hook to allow a "data source
> pre-processor" function to be specified, much in the same way that
> custom bad layer handlers would be. This would be called BEFORE
> resolving any raw paths, and would simply take an input data source
> string and return a processed version of it. (e.g. by replacing
> outdated network paths with their new equivalent). In this way it
> would be transparent to users -- the paths would be automatically
> updated, and then they'd only see the bad layer handler dialog IF an
> updated path still doesn't exist.
>
> Does this sound like what you're seeking?
>
> Nyall

Thanks for your insight Nyall!
Sounds bad with that IT Department. :(

But yes, I think that would be helpful. (I wonder who else hit a wall 
with the broken paths? )
If there would be a way to fix broken paths in python on project init, I 
would use it now to improve on our plugin.

Although my problem (or rather, our problem with Raymond) is similar but 
not exactly the same:

We created a plugin that helps to achieve consistent map layouts: the 
plugin itself ships with the correct resources (logos, symbols, and a 
world map layer)
for specific map layouting tasks and has a simple workflow via dialogs 
that creates the right layouts which the users would fill in or adapt to 
meet their needs.

The problem is that since we ship the resources with the plugin, the 
resource paths are on the plugin path which is different across machines.
So when users create map layouts, save the project file and pass it to 
another user to work on they see missing items and broken layer path, 
even though they have the plugin installed.

On the longer term we believe it would be good to have a resource 
sharing system that is native to QGIS.
That would make it very easy to share resources for plugins as well, 
without needing to instruct the users to get the resource sharing 
plugin, connect to resource repo X and pull resources a, b, c,... and so on.

I had discussions about this with Raymond and we are looking to write a 
QEP about 'native' resource sharing.

We might as well be able to live without a solution for now, as native 
resource sharing would answer our needs best.

Best regards,
Aron
>> Here is my example in code:
>>
>> class MyBadLayerHandler(QgsProjectBadLayerHandler):
>>      """
>>      This is a custom bad layer handler that would work as the default one, except it would automatically fix
>>      a specific layer that is broken.
>>      """
>>
>>      def __init__(self, path):
>>          QgsProjectBadLayerHandler.__init__(self)
>>          self.validLayerPath = path
>>
>>      def handleBadLayers(self, domNodes):
>>          # Some logic to look for a specific DomNode to fix, oversimplified here:
>>          for dom in domNodes:
>>              dataSource = self.dataSource(dom)
>>
>>              if dataSource is 'the broken path to the layer I want to fix':
>>                  # set correct data source then force re-read
>>                  self.setDataSource(domNodes, self.validLayerPath)
>>                  QgsProject.instance().readLayer(dom)
>>
>>          # TODO: after this, call the GUI file dialog as in the default bad layer handler, to allow the user to fix
>>          # any other broken layers... not sure how to do this
>>
>>
>> class MyPlugin:
>>      """My plugin"""
>>
>>      def __init__(self):
>>          self.validPath = 'valid path to layer'
>>          self.badLayerHandler = None
>>
>>      def hackyAndUglyReplacer(self, i, n):
>>          """
>>          This hacky ugly function is to replace the bad layer handler early on, before any layers would be loaded.
>>          it is meant to be connected to the `layerLoaded` signal of `QgsProject`
>>          """
>>          # do not run further if there were other layers loaded before (e.g. the signal was emitted before)
>>          if i != 0:
>>              return
>>
>>          if not self.badLayerHandler:
>>              self.badLayerHandler = MyBadLayerHandler(self.validPath)
>>              QgsProject.instance().setBadLayerHandler(self.badLayerHandler)
>>
>>      def initGui(self):
>>          # start-up code here...
>>
>>          #connect to signal
>>          QgsProject.instance().layerLoaded.connect(self.hackyAndUglyReplacer)
>>
>>      def unload(self):
>>          try:
>>              QgsProject.instance().layerLoaded.disconnect()
>>          except Exception:
>>              pass
>>
>>
>> Does anyone know what am I doing wrong here? Have I missed something?
>> Why does QGIS crash every 2nd time?
>>
>> I would be happy to pass the bad layer handler a better way than with the current signal above. But the other signals I found are emitted 'too late' already.
>> Perhaps here I am also doing something wrong?
>>
>> I tried looking for help but the web barely has any trace of documentation on this.
>>
>> I also tried to see how this is done in other plugins e.g. the changeDataSource plugin, but the authors  seem to have removed the code when they ported the plugin to QGIS 3.x
>> (perhaps a bad sign)
>>
>> In general it seems to me this part was also overhauled in QGIS3 but there aren't many leads to follow on what is the current way of using custom bad layer handlers.
>>
>> Maybe if we could put the story together on how to do this correctly, I could document it and put it on the web for others to refer to.
>>
>> Hope you are having / had a great Sunday!
>>
>> Best regards,
>> Aron
>>
>> _______________________________________________
>> QGIS-Developer mailing list
>> QGIS-Developer at lists.osgeo.org
>> List info:https://lists.osgeo.org/mailman/listinfo/qgis-developer
>> Unsubscribe:https://lists.osgeo.org/mailman/listinfo/qgis-developer



More information about the QGIS-Developer mailing list