瀏覽代碼

wxGUI/data: use watchdog to update tree when mapset is switched externally (cmdline) (#1174)

* wxGUI/data: use watchdog to update tree when mapset is switched externally (cmdline)
* add new mapset node if mapset is newly created
Anna Petrasova 3 年之前
父節點
當前提交
400bef3eb9
共有 1 個文件被更改,包括 74 次插入5 次删除
  1. 74 5
      gui/wxpython/datacatalog/tree.py

+ 74 - 5
gui/wxpython/datacatalog/tree.py

@@ -26,10 +26,11 @@ from multiprocessing import Process, Queue, cpu_count
 watchdog_used = True
 watchdog_used = True
 try:
 try:
     from watchdog.observers import Observer
     from watchdog.observers import Observer
-    from watchdog.events import PatternMatchingEventHandler
+    from watchdog.events import PatternMatchingEventHandler, FileSystemEventHandler
 except ImportError:
 except ImportError:
     watchdog_used = False
     watchdog_used = False
     PatternMatchingEventHandler = object
     PatternMatchingEventHandler = object
+    FileSystemEventHandler = object
 
 
 
 
 import wx
 import wx
@@ -80,6 +81,7 @@ from grass.exceptions import CalledModuleError
 
 
 
 
 updateMapset, EVT_UPDATE_MAPSET = NewEvent()
 updateMapset, EVT_UPDATE_MAPSET = NewEvent()
+currentMapsetChanged, EVT_CURRENT_MAPSET_CHANGED = NewEvent()
 
 
 
 
 def getLocationTree(gisdbase, location, queue, mapsets=None, lazy=False):
 def getLocationTree(gisdbase, location, queue, mapsets=None, lazy=False):
@@ -143,6 +145,42 @@ def getLocationTree(gisdbase, location, queue, mapsets=None, lazy=False):
     gscript.try_remove(tmp_gisrc_file)
     gscript.try_remove(tmp_gisrc_file)
 
 
 
 
+class CurrentMapsetWatch(FileSystemEventHandler):
+    """Monitors rc file to check if mapset has been changed.
+    In that case wx event is dispatched to event handler.
+    Needs to check timestamp, because the modified event is sent twice.
+    This assumes new instance of this class is started
+    whenever mapset is changed."""
+
+    def __init__(self, rcfile, mapset_path, event_handler):
+        FileSystemEventHandler.__init__(self)
+        self.event_handler = event_handler
+        self.mapset_path = mapset_path
+        self.rcfile_name = os.path.basename(rcfile)
+        self.modified_time = 0
+
+    def on_modified(self, event):
+        if (
+            not event.is_directory
+            and os.path.basename(event.src_path) == self.rcfile_name
+        ):
+            timestamp = os.stat(event.src_path).st_mtime
+            if timestamp - self.modified_time < 0.5:
+                return
+            self.modified_time = timestamp
+            with open(event.src_path, "r") as f:
+                gisrc = {}
+                for line in f.readlines():
+                    key, val = line.split(":")
+                    gisrc[key.strip()] = val.strip()
+                new = os.path.join(
+                    gisrc["GISDBASE"], gisrc["LOCATION_NAME"], gisrc["MAPSET"]
+                )
+                if new != self.mapset_path:
+                    evt = currentMapsetChanged()
+                    wx.PostEvent(self.event_handler, evt)
+
+
 class MapWatch(PatternMatchingEventHandler):
 class MapWatch(PatternMatchingEventHandler):
     """Monitors file events (create, delete, move files) using watchdog
     """Monitors file events (create, delete, move files) using watchdog
     to inform about changes in current mapset. One instance monitors
     to inform about changes in current mapset. One instance monitors
@@ -327,7 +365,7 @@ class DataCatalogTree(TreeView):
         self.parent = parent
         self.parent = parent
         self.contextMenu.connect(self.OnRightClick)
         self.contextMenu.connect(self.OnRightClick)
         self.itemActivated.connect(self.OnDoubleClick)
         self.itemActivated.connect(self.OnDoubleClick)
-        self._giface.currentMapsetChanged.connect(self._updateAfterMapsetChanged)
+        self._giface.currentMapsetChanged.connect(self.UpdateAfterMapsetChanged)
         self._giface.grassdbChanged.connect(self._updateAfterGrassdbChanged)
         self._giface.grassdbChanged.connect(self._updateAfterGrassdbChanged)
         self._iconTypes = [
         self._iconTypes = [
             "grassdb",
             "grassdb",
@@ -387,6 +425,9 @@ class DataCatalogTree(TreeView):
             EVT_UPDATE_MAPSET, lambda evt: self._onWatchdogMapsetReload(evt.src_path)
             EVT_UPDATE_MAPSET, lambda evt: self._onWatchdogMapsetReload(evt.src_path)
         )
         )
         self.Bind(wx.EVT_IDLE, self._onUpdateMapsetWhenIdle)
         self.Bind(wx.EVT_IDLE, self._onUpdateMapsetWhenIdle)
+        self.Bind(
+            EVT_CURRENT_MAPSET_CHANGED, lambda evt: self._updateAfterMapsetChanged()
+        )
         self.observer = None
         self.observer = None
 
 
     def _resetSelectVariables(self):
     def _resetSelectVariables(self):
@@ -691,6 +732,7 @@ class DataCatalogTree(TreeView):
         to detect changes not captured by other means (e.g. from command line).
         to detect changes not captured by other means (e.g. from command line).
         Schedules 3 watches (raster, vector, 3D raster).
         Schedules 3 watches (raster, vector, 3D raster).
         If watchdog observers are active, it restarts the observers in current mapset.
         If watchdog observers are active, it restarts the observers in current mapset.
+        Also schedules monitoring of rc file to detect mapset change.
         """
         """
         global watchdog_used
         global watchdog_used
         if not watchdog_used:
         if not watchdog_used:
@@ -703,14 +745,21 @@ class DataCatalogTree(TreeView):
         self.observer = Observer()
         self.observer = Observer()
 
 
         gisenv = gscript.gisenv()
         gisenv = gscript.gisenv()
+        mapset_path = os.path.join(
+            gisenv["GISDBASE"], gisenv["LOCATION_NAME"], gisenv["MAPSET"]
+        )
+        rcfile = os.environ["GISRC"]
+        self.observer.schedule(
+            CurrentMapsetWatch(rcfile, mapset_path, self),
+            os.path.dirname(rcfile),
+            recursive=False,
+        )
         for element, directory in (
         for element, directory in (
             ("raster", "cell"),
             ("raster", "cell"),
             ("vector", "vector"),
             ("vector", "vector"),
             ("raster_3d", "grid3"),
             ("raster_3d", "grid3"),
         ):
         ):
-            path = os.path.join(
-                gisenv["GISDBASE"], gisenv["LOCATION_NAME"], gisenv["MAPSET"], directory
-            )
+            path = os.path.join(mapset_path, directory)
             if not os.path.exists(path):
             if not os.path.exists(path):
                 try:
                 try:
                     os.mkdir(path)
                     os.mkdir(path)
@@ -756,6 +805,15 @@ class DataCatalogTree(TreeView):
             self._reloadMapsetNode(node)
             self._reloadMapsetNode(node)
             self.RefreshNode(node, recursive=True)
             self.RefreshNode(node, recursive=True)
 
 
+    def UpdateAfterMapsetChanged(self):
+        """Wrapper around updating function called
+        after mapset was changed, as a handler of signal.
+        If watchdog is active, updating is skipped here
+        to avoid double updating.
+        """
+        if not watchdog_used:
+            self._updateAfterMapsetChanged()
+
     def GetDbNode(self, grassdb, location=None, mapset=None, map=None, map_type=None):
     def GetDbNode(self, grassdb, location=None, mapset=None, map=None, map_type=None):
         """Returns node representing db/location/mapset/map or None if not found."""
         """Returns node representing db/location/mapset/map or None if not found."""
         grassdb_nodes = self._model.SearchNodes(name=grassdb, type="grassdb")
         grassdb_nodes = self._model.SearchNodes(name=grassdb, type="grassdb")
@@ -1973,6 +2031,17 @@ class DataCatalogTree(TreeView):
 
 
     def _updateAfterMapsetChanged(self):
     def _updateAfterMapsetChanged(self):
         """Update tree after current mapset has changed"""
         """Update tree after current mapset has changed"""
+        db_node, location_node, mapset_node = self.GetCurrentDbLocationMapsetNode()
+        # mapset is not in tree, create its node
+        if not mapset_node:
+            genv = gisenv()
+            if not location_node:
+                if not db_node:
+                    self.InsertGrassDb(genv["GISDBASE"])
+                else:
+                    self.InsertLocation(genv["LOCATION_NAME"], db_node)
+            else:
+                self.InsertMapset(genv["MAPSET"], location_node)
         self.UpdateCurrentDbLocationMapsetNode()
         self.UpdateCurrentDbLocationMapsetNode()
         self._reloadMapsetNode(self.current_mapset_node)
         self._reloadMapsetNode(self.current_mapset_node)
         self.RefreshNode(self.current_mapset_node, recursive=True)
         self.RefreshNode(self.current_mapset_node, recursive=True)