mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
Config.update_config() applies submitted options to the main configuration file via configparser read-modify-write: unknown options are preserved, null removes an option, the merged view of all files is validated as ServerConfig before anything is written (a bad file would kill the FileWatcher polling loop), and the write is atomic (.tmp + os.replace, mode 0600). Options whose effective value is owned by a later configuration file raise ConfigConflictError instead of writing a no-op. The reload logic is factored into reload_and_notify() and the file watcher callback is exception-guarded so polling never dies.
120 lines
3.8 KiB
Python
120 lines
3.8 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
# Copyright (C) 2016 GNS3 Technologies Inc.
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import zlib
|
|
import asyncio
|
|
import os
|
|
|
|
import logging
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class FileWatcher:
|
|
"""
|
|
Watch for file change and call the callback when something happens
|
|
|
|
:param paths: A path or a list of file to watch
|
|
:param delay: Delay between file check (seconds)
|
|
:param strategy: File change strategy (mtime: modification time, hash: hash compute)
|
|
"""
|
|
|
|
def __init__(self, paths, callback, delay=1, strategy="mtime"):
|
|
self._paths = []
|
|
if not isinstance(paths, list):
|
|
paths = [paths]
|
|
for path in paths:
|
|
if not isinstance(path, str):
|
|
path = str(path)
|
|
self._paths.append(path)
|
|
|
|
self._callback = callback
|
|
self._delay = delay
|
|
self._closed = False
|
|
self._strategy = strategy
|
|
|
|
if self._strategy == "mtime":
|
|
# Store modification time
|
|
self._mtime = {}
|
|
for path in self._paths:
|
|
try:
|
|
self._mtime[path] = os.stat(path).st_mtime_ns
|
|
except OSError:
|
|
self._mtime[path] = None
|
|
else:
|
|
# Store hash
|
|
self._hashed = {}
|
|
for path in self._paths:
|
|
try:
|
|
# Alder32 is a fast but insecure hash algorithm
|
|
self._hashed[path] = zlib.adler32(open(path, "rb").read())
|
|
except OSError:
|
|
self._hashed[path] = None
|
|
|
|
try:
|
|
loop = asyncio.get_running_loop()
|
|
except RuntimeError:
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
|
|
loop.call_later(self._delay, self._check_config_file_change)
|
|
|
|
def __del__(self):
|
|
self._closed = True
|
|
|
|
def close(self):
|
|
self._closed = True
|
|
|
|
def _check_config_file_change(self):
|
|
if self._closed:
|
|
return
|
|
changed = False
|
|
|
|
for path in self._paths:
|
|
if self._strategy == "mtime":
|
|
try:
|
|
mtime = os.stat(path).st_mtime_ns
|
|
if mtime != self._mtime[path]:
|
|
changed = True
|
|
self._mtime[path] = mtime
|
|
except OSError:
|
|
self._mtime[path] = None
|
|
else:
|
|
try:
|
|
hashc = zlib.adler32(open(path, "rb").read())
|
|
if hashc != self._hashed[path]:
|
|
changed = True
|
|
self._hashed[path] = hashc
|
|
except OSError:
|
|
self._hashed[path] = None
|
|
if changed:
|
|
try:
|
|
self._callback(path)
|
|
except Exception:
|
|
# never let a callback exception kill the polling loop
|
|
# (the re-schedule below must always run)
|
|
log.exception(f"Error in file watcher callback for '{path}'")
|
|
asyncio.get_event_loop().call_later(self._delay, self._check_config_file_change)
|
|
|
|
@property
|
|
def callback(self):
|
|
return self._callback
|
|
|
|
@callback.setter
|
|
def callback(self, val):
|
|
self._callback = val
|