From 30d027090124c93c0cc4f9d561bed83c0f4374dc Mon Sep 17 00:00:00 2001
From: Dmitry <b4tm4n@mail.ru>
Date: Sat, 2 Mar 2024 18:18:28 +0300
Subject: [PATCH] refactor files filters/outputs

---
 pddnsc/filters/files.py | 80 +++++++++++++++++++++++++----------------
 pddnsc/outputs/files.py | 67 ++++++++++++++++++++++++----------
 2 files changed, 98 insertions(+), 49 deletions(-)

diff --git a/pddnsc/filters/files.py b/pddnsc/filters/files.py
index 67192d2..290c6d2 100644
--- a/pddnsc/filters/files.py
+++ b/pddnsc/filters/files.py
@@ -1,4 +1,3 @@
-import asyncio
 import hashlib
 import json
 import aiofiles
@@ -7,7 +6,41 @@ from os.path import isfile
 from pddnsc.base import BaseFilterProvider
 
 
-class StateHashFilter(BaseFilterProvider):
+class GenericTextFileFilter(BaseFilterProvider):
+    def post_init(self):
+        super().post_init()
+        self.filepath = self.config["filepath"]
+        self.encoding = self.config.get("encoding", "utf-8")
+        self.mode = self.config.get("mode", "r")
+        self.check_ipv4 = self.config.get("check_ipv4", False)
+        self.check_ipv6 = self.config.get("check_ipv6", False)
+        if "check_ipv4" not in self.config and "check_ipv4" not in self.config:
+            self.check_ipv4 = self.check_ipv6 = True
+        self.content = ""
+
+    async def read(self) -> str:
+        async with aiofiles.open(
+            self.filepath, mode=self.mode, encoding=self.encoding
+        ) as f:
+            self.content = await f.read()
+
+    async def check_imp(self, source_provider: str, addr_v4: str, addr_v6: str) -> bool:
+        lst = []
+        if self.check_ipv4:
+            lst.append(addr_v4)
+        if self.check_ipv4:
+            lst.append(addr_v6)
+        new_content = "\n".join(lst)
+        return new_content == self.content
+
+    async def check(self, source_provider: str, addr_v4: str, addr_v6: str) -> bool:
+        if not isfile(self.filepath):
+            return True
+        await self.read()
+        return await self.check_imp(source_provider, addr_v4, addr_v6)
+
+
+class StateHashFilter(GenericTextFileFilter):
     """Проверка на то что хотябы один IP адрес изменился по хешу сохраненному в файле
 
     Конфигурация:
@@ -16,20 +49,14 @@ class StateHashFilter(BaseFilterProvider):
     """
 
     async def check_imp(self, source_provider: str, addr_v4: str, addr_v6: str) -> bool:
-        if not isfile(self.config["filepath"]):
-            return True
-
-        new_state_str = (addr_v4 or "") + (addr_v6 or "")
-        new_sha = hashlib.sha256(new_state_str.encode(encoding="utf-8"))
-        async with aiofiles.open(
-            self.config["filepath"], mode="r", encoding="utf-8"
-        ) as f:
-            old_state_hash = await f.read()
-
-        return old_state_hash != new_sha.hexdigest()
+        new_state_str = (self.check_ipv4 and addr_v4 or "") + (
+            self.check_ipv6 and addr_v6 or ""
+        )
+        new_sha = hashlib.sha256(new_state_str.encode(encoding=self.encoding))
+        return self.content != new_sha.hexdigest()
 
 
-class StateFileFilter(BaseFilterProvider):
+class StateFileFilter(GenericTextFileFilter):
     """Проверка на то что хотябы один IP адрес изменился по сравнению с данными в json файле
 
     Конфигурация:
@@ -42,28 +69,19 @@ class StateFileFilter(BaseFilterProvider):
     """
 
     async def check_imp(self, source_provider: str, addr_v4: str, addr_v6: str) -> bool:
-        if not isfile(self.config["filepath"]):
-            return True
-
-        new_state = {
-            "ipv4": addr_v4,
-            "ipv6": addr_v6,
-        }
-
-        async with aiofiles.open(
-            self.config["filepath"], mode="r", encoding="utf-8"
-        ) as f:
-            old_state = json.loads(await f.read())
+        new_state = {}
+        if self.check_ipv4:
+            new_state["ipv4"] = addr_v4 or ""
+        if self.check_ipv6:
+            new_state["ipv6"] = addr_v6 or ""
 
+        old_state = json.loads(self.content)
         result = True
 
-        if "check_ipv4" not in self.config and "check_ipv4" not in self.config:
-            return new_state != old_state
-
-        if self.config.get("check_ipv4", False):
+        if self.check_ipv4:
             result = result and new_state["ipv4"] != old_state["ipv4"]
 
-        if self.config.get("check_ipv6", False):
+        if self.check_ipv6:
             result = result and new_state["ipv6"] != old_state["ipv6"]
 
         return result
diff --git a/pddnsc/outputs/files.py b/pddnsc/outputs/files.py
index b4c04f5..07a2e6f 100644
--- a/pddnsc/outputs/files.py
+++ b/pddnsc/outputs/files.py
@@ -5,7 +5,42 @@ import hashlib
 from pddnsc.base import BaseOutputProvider
 
 
-class StateFile(BaseOutputProvider):
+class GenericTextFile(BaseOutputProvider):
+    def post_init(self):
+        super().post_init()
+        self.filepath = self.config["filepath"]
+        self.encoding = self.config.get("encoding", "utf-8")
+        self.mode = self.config.get("mode", "w")
+        self.save_ipv4 = self.config.get("save_ipv4", False)
+        self.save_ipv6 = self.config.get("save_ipv6", False)
+        if "save_ipv4" not in self.config and "save_ipv4" not in self.config:
+            self.save_ipv4 = self.save_ipv6 = True
+        self.content = ""
+
+    async def read(self):
+        async with aiofiles.open(self.filepath, mode="r", encoding=self.encoding) as f:
+            self.content = await f.read()
+
+    def set_content(self, ipv4: str, ipv6: str):
+        lst = []
+        if self.save_ipv4:
+            lst.append(ipv4)
+        if self.save_ipv6:
+            lst.append(ipv6)
+        self.content = "\n".join(lst)
+
+    async def write(self):
+        async with aiofiles.open(
+            self.filepath, mode=self.mode, encoding=self.encoding
+        ) as f:
+            await f.write(self.content)
+
+    async def set_addrs_imp(self, source_provider: str, addr_v4: str, addr_v6: str):
+        await self.set_content(addr_v4, addr_v6)
+        await self.write()
+
+
+class StateFile(GenericTextFile):
     """Схранение всех IP адресов в json файл
 
     Конфигурация:
@@ -13,19 +48,16 @@ class StateFile(BaseOutputProvider):
       - filepath: имя файла
     """
 
-    async def set_addrs_imp(self, source_provider: str, addr_v4: str, addr_v6: str):
-        state = {
-            "ipv4": addr_v4 or "",
-            "ipv6": addr_v6 or "",
-        }
-        state_str = json.dumps(state)
-        async with aiofiles.open(
-            self.config["filepath"], mode="w", encoding="utf-8"
-        ) as f:
-            await f.write(state_str)
+    async def set_content(self, addr_v4: str, addr_v6: str):
+        state = {}
+        if self.save_ipv4:
+            state["ipv4"] = addr_v4 or ""
+        if self.save_ipv6:
+            state["ipv6"] = addr_v6 or ""
+        self.content = json.dumps(state)
 
 
-class StateHashFile(BaseOutputProvider):
+class StateHashFile(GenericTextFile):
     """Сохранение хеша от всех IP адресов в файл
 
     Конфигурация:
@@ -33,10 +65,9 @@ class StateHashFile(BaseOutputProvider):
       - filepath: имя файла
     """
 
-    async def set_addrs_imp(self, source_provider: str, addr_v4: str, addr_v6: str):
-        state_str = (addr_v4 or "") + (addr_v6 or "")
+    async def set_content(self, addr_v4: str, addr_v6: str):
+        state_str = (self.save_ipv4 and addr_v4 or "") + (
+            self.save_ipv6 and addr_v6 or ""
+        )
         sha = hashlib.sha256(state_str.encode(encoding="utf-8"))
-        async with aiofiles.open(
-            self.config["filepath"], mode="w", encoding="utf-8"
-        ) as f:
-            await f.write(sha.hexdigest())
+        self.content = sha.hexdigest()