1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
"""
@package core.watchdog
@brief Current mapset and maps watchdog
Classes:
- watchdog::CurrentMapsetWatch
- watchdog::MapWatch
- watchdog::MapsetWatchdog
(C) 2022 by the GRASS Development Team
This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.
@author Anna Kratochvilova <kratochanna gmail.com>
@author Tomas Zigo <tomas.zigo slovanet.sk>
"""
import os
import time
watchdog_used = True
try:
from watchdog.observers import Observer
from watchdog.events import (
PatternMatchingEventHandler,
FileSystemEventHandler,
)
except ImportError:
watchdog_used = False
PatternMatchingEventHandler = object
FileSystemEventHandler = object
import wx
from wx.lib.newevent import NewEvent
from grass.script import core as grass
updateMapset, EVT_UPDATE_MAPSET = NewEvent()
currentMapsetChanged, EVT_CURRENT_MAPSET_CHANGED = NewEvent()
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
# wait to make sure file writing is done
time.sleep(0.1)
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):
"""Monitors file events (create, delete, move files) using watchdog
to inform about changes in current mapset. One instance monitors
only one element (raster, vector, raster_3d).
Patterns are not used/needed in this case, use just '*' for matching
everything. When file/directory change is detected, wx event is dispatched
to event handler (can't use Signals because this is different thread),
containing info about the change."""
def __init__(self, patterns, element, event_handler):
PatternMatchingEventHandler.__init__(self, patterns=patterns)
self.element = element
self.event_handler = event_handler
def on_created(self, event):
if (
self.element == "vector" or self.element == "raster_3d"
) and not event.is_directory:
return
evt = updateMapset(
src_path=event.src_path,
event_type=event.event_type,
is_directory=event.is_directory,
dest_path=None,
)
wx.PostEvent(self.event_handler, evt)
def on_deleted(self, event):
if (
self.element == "vector" or self.element == "raster_3d"
) and not event.is_directory:
return
evt = updateMapset(
src_path=event.src_path,
event_type=event.event_type,
is_directory=event.is_directory,
dest_path=None,
)
wx.PostEvent(self.event_handler, evt)
def on_moved(self, event):
if (
self.element == "vector" or self.element == "raster_3d"
) and not event.is_directory:
return
evt = updateMapset(
src_path=event.src_path,
event_type=event.event_type,
is_directory=event.is_directory,
dest_path=event.dest_path,
)
wx.PostEvent(self.event_handler, evt)
class MapsetWatchdog:
"""Current mapset and maps watchdog
:param tuple elements_dir: tuple of element with dir tuples
(("raster", "cell"), ...)
:param object instance evt_handler: event handler object instance of
class
:param object instance giface: object instance of giface class
:param str patterns: map watchdog patterns with default value "*" all
"""
def __init__(self, elements_dirs, evt_handler, giface, patterns="*"):
self._elements_dirs = elements_dirs
self._evt_handler = evt_handler
self._patterns = patterns
self._giface = giface
self._observer = None
def ScheduleWatchCurrentMapset(self):
"""Using watchdog library, sets up watching of current mapset folder
to detect changes not captured by other means (e.g. from command line).
Schedules 1 watches (raster).
If watchdog observers are active, it restarts the observers in
current mapset.
"""
global watchdog_used
if not watchdog_used:
return
if self._observer and self._observer.is_alive():
self._observer.stop()
self._observer.join()
self._observer.unschedule_all()
self._observer = Observer()
gisenv = grass.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._evt_handler),
os.path.dirname(rcfile),
recursive=False,
)
for element, directory in self._elements_dirs:
path = os.path.join(mapset_path, directory)
if not os.path.exists(path):
try:
os.mkdir(path)
except OSError:
pass
if os.path.exists(path):
self._observer.schedule(
MapWatch(self._patterns, element, self._evt_handler),
path=path,
recursive=False,
)
try:
self._observer.start()
except OSError:
# in case inotify on linux exceeds limits
watchdog_used = False
self._giface.WriteWarning(
_(
"File size limit exceeded. The current mapset"
" and maps watchdog are disabled now."
),
)
return
|