Переглянути джерело

wxGUI datacatalog: Distinguish mapsets by ownership and lock (#849)

* added function in lib/python/grassdb/checks.py which returns the mapset owner.
* change mapset label and color (grey) if there is lock, or owner is different
* change mapset label if current
* rename init variables functions


Co-authored-by: Anna Petrasova <kratochanna@gmail.com>
Linda Kladivova 4 роки тому
батько
коміт
2aab3b6771
2 змінених файлів з 117 додано та 35 видалено
  1. 103 32
      gui/wxpython/datacatalog/tree.py
  2. 14 3
      lib/python/grassdb/checks.py

+ 103 - 32
gui/wxpython/datacatalog/tree.py

@@ -52,6 +52,8 @@ from grass.pydispatch.signal import Signal
 import grass.script as gscript
 import grass.script as gscript
 from grass.script import gisenv
 from grass.script import gisenv
 from grass.grassdb.data import map_exists
 from grass.grassdb.data import map_exists
+from grass.grassdb.checks import (get_mapset_owner, is_mapset_locked,
+                                  is_different_mapset_owner)
 from grass.exceptions import CalledModuleError
 from grass.exceptions import CalledModuleError
 
 
 
 
@@ -205,7 +207,18 @@ class DataCatalogNode(DictNode):
 
 
     @property
     @property
     def label(self):
     def label(self):
-        return self.data["name"]
+        data = self.data
+        if data['type'] == 'mapset':
+            if data['current']:
+                return _("{name}  (current)").format(**data)
+            elif data['is_different_owner'] and data['lock']:
+                return _("{name}  (in use, owner: {owner})").format(**data)
+            elif data['lock']:
+                return _("{name}  (in use)").format(**data)
+            elif data['is_different_owner']:
+                return _("{name}  (owner: {owner})").format(**data)
+
+        return _("{name}").format(**data)
 
 
     def match(self, **kwargs):
     def match(self, **kwargs):
         """Method used for searching according to given parameters.
         """Method used for searching according to given parameters.
@@ -253,8 +266,11 @@ class DataCatalogTree(TreeView):
                            'vector', 'raster_3d']
                            'vector', 'raster_3d']
         self._initImages()
         self._initImages()
 
 
-        self._initVariables()
-        self._initVariablesCatalog()
+        self._resetSelectVariables()
+        self._resetCopyVariables()
+        self.current_grassdb_node = None
+        self.current_location_node = None
+        self.current_mapset_node = None
         self.UpdateCurrentDbLocationMapsetNode()
         self.UpdateCurrentDbLocationMapsetNode()
 
 
         # Get databases from settings
         # Get databases from settings
@@ -284,6 +300,22 @@ class DataCatalogTree(TreeView):
         self.startEdit.connect(self.OnStartEditLabel)
         self.startEdit.connect(self.OnStartEditLabel)
         self.endEdit.connect(self.OnEditLabel)
         self.endEdit.connect(self.OnEditLabel)
 
 
+    def _resetSelectVariables(self):
+        """Reset variables related to item selection."""
+        self.selected_grassdb = []
+        self.selected_layer = []
+        self.selected_mapset = []
+        self.selected_location = []
+        self.mixed = False
+
+    def _resetCopyVariables(self):
+        """Reset copy related variables."""
+        self.copy_mode = False
+        self.copy_layer = None
+        self.copy_mapset = None
+        self.copy_location = None
+        self.copy_grassdb = None
+
     def _getValidSavedGrassDBs(self):
     def _getValidSavedGrassDBs(self):
         """Returns list of GRASS databases from settings.
         """Returns list of GRASS databases from settings.
         Returns only existing directories."""
         Returns only existing directories."""
@@ -342,10 +374,20 @@ class DataCatalogTree(TreeView):
                 None))
                 None))
         p.start()
         p.start()
         maps, error = q.get()
         maps, error = q.get()
+
         for mapset in maps:
         for mapset in maps:
+            mapset_path = os.path.join(location_node.parent.data['name'],
+                                       location_node.data['name'],
+                                       mapset)
+
             mapset_node = self._model.AppendNode(
             mapset_node = self._model.AppendNode(
                                 parent=location_node,
                                 parent=location_node,
-                                data=dict(type='mapset', name=mapset))
+                                data=dict(type='mapset',
+                                          name=mapset,
+                                          current=False,
+                                          lock=is_mapset_locked(mapset_path),
+                                          is_different_owner=is_different_mapset_owner(mapset_path),
+                                          owner=get_mapset_owner(mapset_path)))
             self._populateMapsetItem(mapset_node,
             self._populateMapsetItem(mapset_node,
                                      maps[mapset])
                                      maps[mapset])
         self._model.SortChildren(location_node)
         self._model.SortChildren(location_node)
@@ -357,7 +399,7 @@ class DataCatalogTree(TreeView):
         Runs reloading locations in parallel."""
         Runs reloading locations in parallel."""
         if grassdb_node.children:
         if grassdb_node.children:
             del grassdb_node.children[:]
             del grassdb_node.children[:]
-        locations = GetListOfLocations(grassdb_node.data["name"])
+        locations = GetListOfLocations(grassdb_node.data['name'])
 
 
         loc_count = proc_count = 0
         loc_count = proc_count = 0
         queue_list = []
         queue_list = []
@@ -387,7 +429,7 @@ class DataCatalogTree(TreeView):
 
 
             q = Queue()
             q = Queue()
             p = Process(target=getLocationTree,
             p = Process(target=getLocationTree,
-                        args=(grassdb_node.data["name"], location, q))
+                        args=(grassdb_node.data['name'], location, q))
             p.start()
             p.start()
 
 
             queue_list.append(q)
             queue_list.append(q)
@@ -405,9 +447,17 @@ class DataCatalogTree(TreeView):
                         errors.append(error)
                         errors.append(error)
 
 
                     for key in sorted(maps.keys()):
                     for key in sorted(maps.keys()):
+                        mapset_path = os.path.join(location_nodes[i].parent.data['name'],
+                                                   location_nodes[i].data['name'],
+                                                   key)
                         mapset_node = self._model.AppendNode(
                         mapset_node = self._model.AppendNode(
                                 parent=location_nodes[i],
                                 parent=location_nodes[i],
-                                data=dict(type='mapset', name=key))
+                                data=dict(type='mapset',
+                                          name=key,
+                                          lock=is_mapset_locked(mapset_path),
+                                          current=False,
+                                          is_different_owner=is_different_mapset_owner(mapset_path),
+                                          owner=get_mapset_owner(mapset_path)))
                         self._populateMapsetItem(mapset_node, maps[key])
                         self._populateMapsetItem(mapset_node, maps[key])
 
 
                 proc_count = 0
                 proc_count = 0
@@ -454,9 +504,27 @@ class DataCatalogTree(TreeView):
         self.RefreshNode(node.parent, recursive=True)
         self.RefreshNode(node.parent, recursive=True)
 
 
     def UpdateCurrentDbLocationMapsetNode(self):
     def UpdateCurrentDbLocationMapsetNode(self):
+        """Update variables storing current mapset/location/grassdb node.
+        Updates associated mapset node data ('lock' and 'current').
+        """
+
+        def is_current_mapset_node_locked():
+            mapset_path = os.path.join(self.current_grassdb_node.data['name'],
+                                       self.current_location_node.data['name'],
+                                       self.current_mapset_node.data["name"])
+            return is_mapset_locked(mapset_path)
+
+        if self.current_mapset_node:
+            self.current_mapset_node.data["current"] = False
+            self.current_mapset_node.data["lock"] = is_current_mapset_node_locked()
+
         self.current_grassdb_node, self.current_location_node, self.current_mapset_node = \
         self.current_grassdb_node, self.current_location_node, self.current_mapset_node = \
             self.GetCurrentDbLocationMapsetNode()
             self.GetCurrentDbLocationMapsetNode()
 
 
+        if self.current_mapset_node:
+            self.current_mapset_node.data["current"] = True
+            self.current_mapset_node.data["lock"] = is_current_mapset_node_locked()
+
     def ReloadTreeItems(self):
     def ReloadTreeItems(self):
         """Reload dbs, locations, mapsets and layers in the tree."""
         """Reload dbs, locations, mapsets and layers in the tree."""
         self._reloadTreeItems()
         self._reloadTreeItems()
@@ -476,14 +544,6 @@ class DataCatalogTree(TreeView):
                                    data=dict(**item))
                                    data=dict(**item))
         self._model.SortChildren(mapset_node)
         self._model.SortChildren(mapset_node)
 
 
-    def _initVariables(self):
-        """Init variables."""
-        self.selected_grassdb = []
-        self.selected_layer = []
-        self.selected_mapset = []
-        self.selected_location = []
-        self.mixed = False
-
     def _initImages(self):
     def _initImages(self):
         bmpsize = (16, 16)
         bmpsize = (16, 16)
         icons = {
         icons = {
@@ -505,7 +565,7 @@ class DataCatalogTree(TreeView):
 
 
     def DefineItems(self, selected):
     def DefineItems(self, selected):
         """Set selected items."""
         """Set selected items."""
-        self._initVariables()
+        self._resetSelectVariables()
         mixed = []
         mixed = []
         for item in selected:
         for item in selected:
             type = item.data['type']
             type = item.data['type']
@@ -613,7 +673,8 @@ class DataCatalogTree(TreeView):
 
 
         mapsetItem = self._model.SearchNodes(
         mapsetItem = self._model.SearchNodes(
             parent=locationItem[0],
             parent=locationItem[0],
-            name=mapset, type='mapset')
+            name=mapset,
+            type='mapset')
         if not mapsetItem:
         if not mapsetItem:
             return grassdbItem[0], locationItem[0], None
             return grassdbItem[0], locationItem[0], None
 
 
@@ -627,6 +688,17 @@ class DataCatalogTree(TreeView):
         except ValueError:
         except ValueError:
             return 0
             return 0
 
 
+    def OnGetItemTextColour(self, index):
+        """Overriden method to return colour for each item.
+           Used to distinquish lock and ownership on mapsets."""
+        node = self._model.GetNodeByIndex(index)
+        if node.data['type'] == 'mapset':
+            if node.data['current']:
+                return wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
+            elif node.data['lock'] or node.data['is_different_owner']:
+                return wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)
+        return wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
+
     def OnGetItemFont(self, index):
     def OnGetItemFont(self, index):
         """Overriden method to return font for each item.
         """Overriden method to return font for each item.
            Used to highlight current db/loc/mapset."""
            Used to highlight current db/loc/mapset."""
@@ -645,14 +717,6 @@ class DataCatalogTree(TreeView):
             self.Select(self.current_mapset_node, select=True)
             self.Select(self.current_mapset_node, select=True)
             self.ExpandNode(self.current_mapset_node, recursive=True)
             self.ExpandNode(self.current_mapset_node, recursive=True)
 
 
-    def _initVariablesCatalog(self):
-        """Init variables."""
-        self.copy_mode = False
-        self.copy_layer = None
-        self.copy_mapset = None
-        self.copy_location = None
-        self.copy_grassdb = None
-
     def SetRestriction(self, restrict):
     def SetRestriction(self, restrict):
         self._restricted = restrict
         self._restricted = restrict
 
 
@@ -708,12 +772,11 @@ class DataCatalogTree(TreeView):
 
 
     def CreateMapset(self, grassdb_node, location_node):
     def CreateMapset(self, grassdb_node, location_node):
         """Creates new mapset interactively and adds it to the tree."""
         """Creates new mapset interactively and adds it to the tree."""
-        mapset = create_mapset_interactively(self, grassdb_node.data["name"],
-                                             location_node.data["name"])
+        mapset = create_mapset_interactively(self, grassdb_node.data['name'],
+                                             location_node.data['name'])
         if mapset:
         if mapset:
             self.InsertMapset(name=mapset,
             self.InsertMapset(name=mapset,
                               location_node=location_node)
                               location_node=location_node)
-            self.ReloadTreeItems()
 
 
     def OnCreateMapset(self, event):
     def OnCreateMapset(self, event):
         """Create new mapset"""
         """Create new mapset"""
@@ -905,7 +968,7 @@ class DataCatalogTree(TreeView):
                 if dlg.ShowModal() == wx.ID_CANCEL:
                 if dlg.ShowModal() == wx.ID_CANCEL:
                     return
                     return
         self.ExpandNode(self.selected_mapset[0], recursive=True)
         self.ExpandNode(self.selected_mapset[0], recursive=True)
-        self._initVariablesCatalog()
+        self._resetCopyVariables()
 
 
     def _onDoneReprojection(self, iEnv, iGisrc, oGisrc, cLayer, cMapset, cMode, name):
     def _onDoneReprojection(self, iEnv, iGisrc, oGisrc, cLayer, cMapset, cMode, name):
         self.InsertLayer(name=name, mapset_node=self.selected_mapset[0],
         self.InsertLayer(name=name, mapset_node=self.selected_mapset[0],
@@ -935,8 +998,16 @@ class DataCatalogTree(TreeView):
     def InsertMapset(self, name, location_node):
     def InsertMapset(self, name, location_node):
         """Insert new mapset into model and refresh tree.
         """Insert new mapset into model and refresh tree.
         Assumes mapset is empty."""
         Assumes mapset is empty."""
+        mapset_path = os.path.join(location_node.parent.data['name'],
+                                   location_node.data['name'],
+                                   name)
         mapset_node = self._model.AppendNode(parent=location_node,
         mapset_node = self._model.AppendNode(parent=location_node,
-                                             data=dict(type="mapset", name=name))
+                                             data=dict(type='mapset',
+                                                       name=name,
+                                                       current=False,
+                                                       lock=is_mapset_locked(mapset_path),
+                                                       is_different_owner=is_different_mapset_owner(mapset_path),
+                                                       owner=get_mapset_owner(mapset_path)))
         self._model.SortChildren(location_node)
         self._model.SortChildren(location_node)
         self.RefreshNode(location_node, recursive=True)
         self.RefreshNode(location_node, recursive=True)
         return mapset_node
         return mapset_node
@@ -944,7 +1015,7 @@ class DataCatalogTree(TreeView):
     def InsertLocation(self, name, grassdb_node):
     def InsertLocation(self, name, grassdb_node):
         """Insert new location into model and refresh tree"""
         """Insert new location into model and refresh tree"""
         location_node = self._model.AppendNode(parent=grassdb_node,
         location_node = self._model.AppendNode(parent=grassdb_node,
-                                               data=dict(type="location", name=name))
+                                               data=dict(type='location', name=name))
         # reload new location since it has a mapset
         # reload new location since it has a mapset
         self._reloadLocationNode(location_node)
         self._reloadLocationNode(location_node)
         self._model.SortChildren(grassdb_node)
         self._model.SortChildren(grassdb_node)
@@ -957,7 +1028,7 @@ class DataCatalogTree(TreeView):
         Check if not already added.
         Check if not already added.
         """
         """
         grassdb_node = self._model.SearchNodes(name=name,
         grassdb_node = self._model.SearchNodes(name=name,
-                                                   type='grassdb')
+                                               type='grassdb')
         if not grassdb_node:
         if not grassdb_node:
             grassdb_node = self._model.AppendNode(parent=self._model.root,
             grassdb_node = self._model.AppendNode(parent=self._model.root,
                                                   data=dict(type="grassdb", name=name))
                                                   data=dict(type="grassdb", name=name))

+ 14 - 3
lib/python/grassdb/checks.py

@@ -11,6 +11,7 @@ for details.
 
 
 
 
 import os
 import os
+from pwd import getpwuid
 
 
 
 
 def mapset_exists(database, location, mapset):
 def mapset_exists(database, location, mapset):
@@ -57,8 +58,8 @@ def is_location_valid(database, location):
     )
     )
 
 
 
 
-def is_mapset_users(mapset_path):
-    """Check if the mapset belongs to the user"""
+def is_current_user_mapset_owner(mapset_path):
+    """Returns True if mapset owner is the current user"""
     # Note that this does account for libgis built with SKIP_MAPSET_OWN_CHK
     # Note that this does account for libgis built with SKIP_MAPSET_OWN_CHK
     # which disables the ownerships check, i.e., even if it was build with the
     # which disables the ownerships check, i.e., even if it was build with the
     # skip, it still needs the env variable.
     # skip, it still needs the env variable.
@@ -71,6 +72,16 @@ def is_mapset_users(mapset_path):
     return mapset_uid == os.getuid()
     return mapset_uid == os.getuid()
 
 
 
 
+def is_different_mapset_owner(mapset_path):
+    """Returns True if mapset owner is different from the current user"""
+    return not is_current_user_mapset_owner(mapset_path)
+
+
+def get_mapset_owner(mapset_path):
+    """Returns mapset owner"""
+    return getpwuid(os.stat(mapset_path).st_uid).pw_name
+
+
 def is_mapset_locked(mapset_path):
 def is_mapset_locked(mapset_path):
     """Check if the mapset is locked"""
     """Check if the mapset is locked"""
     lock_name = ".gislock"
     lock_name = ".gislock"
@@ -95,7 +106,7 @@ def can_start_in_mapset(mapset_path, ignore_lock=False):
     """Check if a mapset from a gisrc file is usable for new session"""
     """Check if a mapset from a gisrc file is usable for new session"""
     if not is_mapset_valid(mapset_path):
     if not is_mapset_valid(mapset_path):
         return False
         return False
-    if not is_mapset_users(mapset_path):
+    if not is_current_user_mapset_owner(mapset_path):
         return False
         return False
     if not ignore_lock and is_mapset_locked(mapset_path):
     if not ignore_lock and is_mapset_locked(mapset_path):
         return False
         return False