diff --git a/cpu_governor_auto_adjust.xml b/cpu_governor_auto_adjust.xml index ad24570..e5b9e0f 100644 --- a/cpu_governor_auto_adjust.xml +++ b/cpu_governor_auto_adjust.xml @@ -66,7 +66,13 @@ /home/martin/dev/roon_arc/RoonServer_log.txt 1 - [Broker:Mobile] + + [Broker:Mobile] + + AND + got playbackinfo + + diff --git a/cpu_governor_auto_adjust/triggers/log.py b/cpu_governor_auto_adjust/triggers/log.py index fc31606..a8f76f6 100644 --- a/cpu_governor_auto_adjust/triggers/log.py +++ b/cpu_governor_auto_adjust/triggers/log.py @@ -2,14 +2,46 @@ from cpu_governor_auto_adjust.trigger import Trigger from cpu_governor_auto_adjust.config import Config from datetime import datetime, timedelta from functools import cached_property +from dataclasses import dataclass import asyncio import os +@dataclass +class TriggerString: + json_data: dict[str, str] + + @cached_property + def string(self) -> str: + if 'string' in self.json_data: + return self.json_data['string'] + else: + raise ValueError("TriggerString must contain 'string'") + + @cached_property + def substrings(self) -> list[str]: + if 'substrings' in self.json_data: + return self.json_data['substrings']['substring'] + return [] + + @cached_property + def condition(self) -> str: + if not self.substrings: + return 'OR' + if not 'condition' in self.json_data['substrings']: + raise ValueError("substrings must contain 'condition'") + if not self.json_data['substrings']['condition'] in ['OR', 'AND']: + raise ValueError("substrings condition must be 'OR' or 'AND'") + return self.json_data['substrings']['condition'] + + def __repr__(self) -> str: + return f"TriggerString(string={self.string}, substrings={self.substrings}, condition={self.condition})" + class LogTrigger(Trigger): def __init__(self, _config: Config) -> None: super().__init__(_config) self.timestamp_last_active_change = datetime.now() - timedelta(minutes=self.timeout_in_minutes + 1) + self.line_count = 0 @cached_property def file(self) -> str: @@ -20,36 +52,60 @@ class LogTrigger(Trigger): return float(self.config.custom_config['timeoutInMinutes']) @cached_property - def trigger_words(self) -> list: - _trigger_words = [] - if 'triggerWord' not in self.config.custom_config: - self.config.custom_config['triggerWords'] = _trigger_words - elif isinstance(self.config.custom_config['triggerWord'], str): - _trigger_words.append(self.config.custom_config['triggerWord']) - elif isinstance(self.config.custom_config['triggerWord'], list): - _trigger_words = self.config.custom_config['triggerWord'] + def trigger_strings(self) -> list[TriggerString]: + _trigger_strings = [] + if 'triggerString' not in self.config.custom_config: + return _trigger_strings + elif isinstance(self.config.custom_config['triggerString'], dict): + _trigger_strings.append(TriggerString(self.config.custom_config['triggerString'])) + elif isinstance(self.config.custom_config['triggerString'], list): + for trigger_string in self.config.custom_config['triggerString']: + _trigger_strings.append(TriggerString(trigger_string)) else: - raise ValueError("triggerWord must be a string or a list of strings") - return _trigger_words + raise ValueError("triggerString must be a dict or a list of dicts") + return _trigger_strings def set_active(self) -> None: self.timestamp_last_active_change = datetime.now() if not self.active: - self.log.info("activating trigger, log file: %s, trigger words: %s, governor: %s", self.file, self.trigger_words, self.governor.name) + self.log.info("activating trigger, log file: %s, trigger strings: %s, governor: %s", self.file, self.trigger_strings, self.governor.name) self.active = True def set_inactive(self) -> None: if self.active: - self.log.info("deactivating trigger, log file: %s, trigger words: %s, governor: %s", self.file, self.trigger_words, self.governor.name) + self.log.info("deactivating trigger, log file: %s, trigger strings: %s, governor: %s", self.file, self.trigger_strings, self.governor.name) self.active = False def _process_line(self, line: str) -> None: - for word in self.trigger_words: - if word in line: - self.log.debug(line.strip()) - self.set_active() - return + for trigger_string in self.trigger_strings: + if trigger_string.string in line: + if not trigger_string.substrings: + self.log.debug(line.strip()) + self.set_active() + return + elif trigger_string.substrings and trigger_string.condition == 'OR': + if any(substring in line for substring in trigger_string.substrings): + self.log.debug(line.strip()) + self.set_active() + return + elif trigger_string.substrings and trigger_string.condition == 'AND': + if all(substring in line for substring in trigger_string.substrings): + self.log.debug(line.strip()) + self.set_active() + return + else: + self.log.debug("substrings condition not met") + self.log.debug("No trigger strings matched in line: %s", line.strip()) + def get_statistic_info(self) -> dict[str, str]: + return { + 'file': self.file, + 'active': str(self.active), + 'line_count': str(self.line_count), + 'timestamp_last_active_change': str(self.timestamp_last_active_change), + 'timeout_in_minutes': str(self.timeout_in_minutes), + } + async def read_log(self) -> None: while True: try: @@ -60,6 +116,12 @@ class LogTrigger(Trigger): while True: line = _file.readline() if line: + self.line_count += 1 + if self.line_count % 100 == 0: + self.log.info("Statistics: %s", self.get_statistic_info()) + else: + self.log.debug("Statistics: %s", self.get_statistic_info()) + self._process_line(line) else: await asyncio.sleep(0.1) @@ -69,7 +131,7 @@ class LogTrigger(Trigger): # Detect if file has been rotated if os.stat(self.file).st_ino != current_inode: - self.log.debug("File %s rolled over. Reopening...", self.file) + self.log.info("File %s rolled over. Reopening...", self.file) break # Exit inner loop to reopen file except FileNotFoundError: self.log.debug("file not found: %s", self.file) diff --git a/pyproject.toml b/pyproject.toml index ce8356b..ffad25b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cpu_governor_auto_adjust" -version = "0.2.2" +version = "0.2.5" description = "This application has been developed to automatically change cpu governor based on certain triggers." authors = [ { name = "Martin Reurekas", email = "martin@semrks.nl" }