瀏覽代碼

wxGUI: Ask to remove lock when switching to another mapset from datacatalog and menu (#906)

Linda Kladivova 4 年之前
父節點
當前提交
e391950390
共有 4 個文件被更改,包括 127 次插入35 次删除
  1. 25 24
      gui/wxpython/datacatalog/tree.py
  2. 16 2
      gui/wxpython/lmgr/frame.py
  3. 67 9
      gui/wxpython/startup/guiutils.py
  4. 19 0
      lib/python/grassdb/checks.py

+ 25 - 24
gui/wxpython/datacatalog/tree.py

@@ -44,7 +44,8 @@ from startup.guiutils import (
     delete_mapsets_interactively,
     delete_locations_interactively,
     download_location_interactively,
-    delete_grassdb_interactively
+    delete_grassdb_interactively,
+    can_switch_mapset_interactive
 )
 
 from grass.pydispatch.signal import Signal
@@ -1259,30 +1260,30 @@ class DataCatalogTree(TreeView):
                 event.Veto()
                 return
 
-    def OnSwitchDbLocationMapset(self, event):
-        """Switch to location and mapset"""
-        self._SwitchDbLocationMapset()
-
-    def _SwitchDbLocationMapset(self):
+    def OnSwitchMapset(self, event):
         """Switch to location and mapset"""
         genv = gisenv()
-        # Distinguish when only part of db/location/mapset is changed.
-        if (
-            self.selected_grassdb[0].data['name'] == genv['GISDBASE']
-            and self.selected_location[0].data['name'] == genv['LOCATION_NAME']
-        ):
-            self.changeMapset.emit(mapset=self.selected_mapset[0].data['name'])
-        elif self.selected_grassdb[0].data['name'] == genv['GISDBASE']:
-            self.changeLocation.emit(mapset=self.selected_mapset[0].data['name'],
-                                     location=self.selected_location[0].data['name'],
-                                     dbase=None)
-        else:
-            self.changeLocation.emit(mapset=self.selected_mapset[0].data['name'],
-                                     location=self.selected_location[0].data['name'],
-                                     dbase=self.selected_grassdb[0].data['name'])
-        self.UpdateCurrentDbLocationMapsetNode()
-        self.ExpandCurrentMapset()
-        self.RefreshItems()
+        grassdb = self.selected_grassdb[0].data['name']
+        location = self.selected_location[0].data['name']
+        mapset = self.selected_mapset[0].data['name']
+
+        if can_switch_mapset_interactive(self, grassdb, location, mapset):
+            # Switch to mapset in the same location
+            if (grassdb == genv['GISDBASE'] and location == genv['LOCATION_NAME']):
+                self.changeMapset.emit(mapset=mapset)
+            # Switch to mapset in the same grassdb
+            elif grassdb == genv['GISDBASE']:
+                self.changeLocation.emit(mapset=mapset,
+                                         location=location,
+                                         dbase=None)
+            # Switch to mapset in a different grassdb
+            else:
+                self.changeLocation.emit(mapset=mapset,
+                                         location=location,
+                                         dbase=grassdb)
+            self.UpdateCurrentDbLocationMapsetNode()
+            self.ExpandCurrentMapset()
+            self.RefreshItems()
 
     def OnMetadata(self, event):
         """Show metadata of any raster/vector/3draster"""
@@ -1452,7 +1453,7 @@ class DataCatalogTree(TreeView):
 
         item = wx.MenuItem(menu, wx.ID_ANY, _("&Switch mapset"))
         menu.AppendItem(item)
-        self.Bind(wx.EVT_MENU, self.OnSwitchDbLocationMapset, item)
+        self.Bind(wx.EVT_MENU, self.OnSwitchMapset, item)
         if (
             self.selected_grassdb[0].data['name'] == genv['GISDBASE']
             and self.selected_location[0].data['name'] == genv['LOCATION_NAME']

+ 16 - 2
gui/wxpython/lmgr/frame.py

@@ -43,6 +43,9 @@ if os.path.join(globalvar.ETCDIR, "python") not in sys.path:
 
 from grass.script import core as grass
 from grass.script.utils import decode
+from startup.guiutils import (
+    can_switch_mapset_interactive
+)
 
 from core.gcmd import RunCommand, GError, GMessage
 from core.settings import UserSettings, GetDisplayVectSettings
@@ -1062,6 +1065,8 @@ class GMFrame(wx.Frame):
     def OnChangeLocation(self, event):
         """Change current location"""
         dlg = LocationDialog(parent=self)
+        gisenv = grass.gisenv()
+
         if dlg.ShowModal() == wx.ID_OK:
             location, mapset = dlg.GetValues()
             dlg.Destroy()
@@ -1072,7 +1077,11 @@ class GMFrame(wx.Frame):
                     message=_(
                         "No location/mapset provided. Operation canceled."))
                 return  # this should not happen
-            self.ChangeLocation(location, mapset)
+            if can_switch_mapset_interactive(self,
+                                             gisenv['GISDBASE'],
+                                             location,
+                                             mapset):
+                self.ChangeLocation(location, mapset)
 
     def ChangeLocation(self, location, mapset, dbase=None):
         if dbase:
@@ -1132,6 +1141,7 @@ class GMFrame(wx.Frame):
     def OnChangeMapset(self, event):
         """Change current mapset"""
         dlg = MapsetDialog(parent=self)
+        gisenv = grass.gisenv()
 
         if dlg.ShowModal() == wx.ID_OK:
             mapset = dlg.GetMapset()
@@ -1141,7 +1151,11 @@ class GMFrame(wx.Frame):
                 GError(parent=self,
                        message=_("No mapset provided. Operation canceled."))
                 return
-            self.ChangeMapset(mapset)
+            if can_switch_mapset_interactive(self,
+                                             gisenv['GISDBASE'],
+                                             gisenv['LOCATION_NAME'],
+                                             mapset):
+                self.ChangeMapset(mapset)
 
     def ChangeMapset(self, mapset):
         """Change current mapset and update map display title"""

+ 67 - 9
gui/wxpython/startup/guiutils.py

@@ -21,7 +21,11 @@ import wx
 
 import grass.script as gs
 from grass.script import gisenv
-from grass.grassdb.checks import mapset_exists, location_exists
+from grass.grassdb.checks import (
+    mapset_exists,
+    location_exists,
+    is_mapset_locked,
+    get_mapset_lock_info)
 from grass.grassdb.create import create_mapset, get_default_mapset_name
 from grass.grassdb.manage import (
     delete_mapset,
@@ -646,14 +650,14 @@ def delete_grassdb_interactively(guiparent, grassdb):
 
     if issue:
         dlg = wx.MessageDialog(
-        parent=guiparent,
-        message=_(
-            "Cannot delete GRASS database from disk for the following reason:\n\n"
-            "{}\n\n"
-            "GRASS database will not be deleted."
-        ).format(issue),
-        caption=_("Unable to delete selected GRASS database"),
-        style=wx.OK | wx.ICON_WARNING
+            parent=guiparent,
+            message=_(
+                "Cannot delete GRASS database from disk for the following reason:\n\n"
+                "{}\n\n"
+                "GRASS database will not be deleted."
+            ).format(issue),
+            caption=_("Unable to delete selected GRASS database"),
+            style=wx.OK | wx.ICON_WARNING
         )
         dlg.ShowModal()
     else:
@@ -692,6 +696,60 @@ def delete_grassdb_interactively(guiparent, grassdb):
     return deleted
 
 
+def can_switch_mapset_interactive(guiparent, grassdb, location, mapset):
+    """
+    Checks if mapset is locked and offers to remove the lock file.
+
+    Returns True if user wants to switch to the selected mapset in spite of
+    removing lock. Returns False if a user wants to stay in the current
+    mapset or if an error was encountered.
+    """
+    can_switch = True
+    mapset_path = os.path.join(grassdb, location, mapset)
+
+    if is_mapset_locked(mapset_path):
+        info = get_mapset_lock_info(mapset_path)
+        user = info['owner'] if info['owner'] else _('unknown')
+        lockpath = info['lockpath']
+        timestamp = info['timestamp']
+
+        dlg = wx.MessageDialog(
+            parent=guiparent,
+            message=_("User {user} is already running GRASS in selected mapset "
+                      "<{mapset}>\n (file {lockpath} created {timestamp} "
+                      "found).\n\n"
+                      "Concurrent use not allowed.\n\n"
+                      "Do you want to stay in the current mapset or remove "
+                      ".gislock and switch to selected mapset?"
+                      ).format(user=user,
+                               mapset=mapset,
+                               lockpath=lockpath,
+                               timestamp=timestamp),
+            caption=_("Mapset is in use"),
+            style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
+        )
+        dlg.SetYesNoLabels("S&witch to selected mapset",
+                           "S&tay in current mapset")
+        if dlg.ShowModal() == wx.ID_YES:
+            # Remove lockfile
+            try:
+                os.remove(lockpath)
+            except IOError as e:
+                wx.MessageBox(
+                    parent=guiparent,
+                    caption=_("Error when removing lock file"),
+                    message=_("Unable to remove {lockpath}.\n\n Details: {error}."
+                              ).format(lockpath=lockpath,
+                                       error=e),
+                    style=wx.OK | wx.ICON_ERROR | wx.CENTRE
+                )
+                can_switch = False
+        else:
+            can_switch = False
+        dlg.Destroy()
+    return can_switch
+
+
 def import_file(guiparent, filePath):
     """Tries to import file as vector or raster.
 

+ 19 - 0
lib/python/grassdb/checks.py

@@ -11,6 +11,7 @@ for details.
 
 
 import os
+import datetime
 from pathlib import Path
 
 
@@ -106,6 +107,24 @@ def get_lockfile_if_present(database, location, mapset):
     return None
 
 
+def get_mapset_lock_info(mapset_path):
+    """Get information about .gislock file.
+    Assumes lock file exists, use is_mapset_locked to find out.
+    Returns information as a dictionary with keys
+    'owner' (None if unknown), 'lockpath', and 'timestamp'.
+    """
+    info = {}
+    lock_name = ".gislock"
+    info['lockpath'] = os.path.join(mapset_path, lock_name)
+    try:
+        info['owner'] = Path(info['lockpath']).owner()
+    except KeyError:
+        info['owner'] = None
+    info['timestamp'] = (datetime.datetime.fromtimestamp(
+        os.path.getmtime(info['lockpath']))).replace(microsecond=0)
+    return info
+
+
 def can_start_in_mapset(mapset_path, ignore_lock=False):
     """Check if a mapset from a gisrc file is usable for new session"""
     if not is_mapset_valid(mapset_path):