From 182a979e717b2482d8194095c3232bf7eefe24d1 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Fri, 10 Jun 2016 17:51:19 +0200 Subject: [PATCH] Generic class for watch file change --- gns3server/config.py | 25 ++++-------- gns3server/utils/file_watcher.py | 68 ++++++++++++++++++++++++++++++++ tests/utils/test_file_watcher.py | 46 +++++++++++++++++++++ 3 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 gns3server/utils/file_watcher.py create mode 100644 tests/utils/test_file_watcher.py diff --git a/gns3server/config.py b/gns3server/config.py index bd47931d..63516d95 100644 --- a/gns3server/config.py +++ b/gns3server/config.py @@ -24,6 +24,8 @@ import os import configparser import asyncio +from .utils.file_watcher import FileWatcher + import logging log = logging.getLogger(__name__) @@ -100,24 +102,13 @@ class Config(object): self.read_config() def _watch_config_file(self): - asyncio.get_event_loop().call_later(1, self._check_config_file_change) + for file in self._files: + self._watched_files[file] = FileWatcher(file, self._config_file_change) - def _check_config_file_change(self): - """ - Check if configuration file has changed on the disk - """ - changed = False - for file in self._watched_files: - try: - if os.stat(file).st_mtime != self._watched_files[file]: - changed = True - except OSError: - continue - if changed: - self.read_config() - for section in self._override_config: - self.set_section_config(section, self._override_config[section]) - self._watch_config_file() + def _config_file_change(self, path): + self.read_config() + for section in self._override_config: + self.set_section_config(section, self._override_config[section]) def reload(self): """ diff --git a/gns3server/utils/file_watcher.py b/gns3server/utils/file_watcher.py new file mode 100644 index 00000000..0ca7c101 --- /dev/null +++ b/gns3server/utils/file_watcher.py @@ -0,0 +1,68 @@ +#!/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 . + +import asyncio +import os + + +class FileWatcher: + """ + Watch for file change and call the callback when something happen + """ + def __init__(self, path, callback, delay=1): + if not isinstance(path, str): + path = str(path) + self._path = path + self._callback = callback + self._delay = delay + self._closed = False + + try: + self._mtime = os.stat(path).st_mtime + except OSError: + self._mtime = None + asyncio.get_event_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 + try: + if os.stat(self._path).st_mtime != self._mtime: + changed = True + self._mtime = os.stat(self._path).st_mtime + except OSError: + self._mtime = None + if changed: + self._callback(self._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 + + diff --git a/tests/utils/test_file_watcher.py b/tests/utils/test_file_watcher.py new file mode 100644 index 00000000..84e86bc6 --- /dev/null +++ b/tests/utils/test_file_watcher.py @@ -0,0 +1,46 @@ +#!/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 . + +import asyncio +from unittest.mock import MagicMock + + +from gns3server.utils.file_watcher import FileWatcher + + +def test_file_watcher(async_run, tmpdir): + file = tmpdir / "test" + file.write("a") + callback = MagicMock() + fw = FileWatcher(file, callback, delay=0.5) + async_run(asyncio.sleep(1)) + assert not callback.called + file.write("b") + async_run(asyncio.sleep(1.5)) + callback.assert_called_with(str(file)) + + +def test_file_watcher_not_existing(async_run, tmpdir): + file = tmpdir / "test" + callback = MagicMock() + fw = FileWatcher(file, callback, delay=0.5) + async_run(asyncio.sleep(1)) + assert not callback.called + file.write("b") + async_run(asyncio.sleep(1.5)) + callback.assert_called_with(str(file)) +