feature: custom software suggestions

This commit is contained in:
Vinicius Moreira
2022-05-26 18:18:09 -03:00
parent 9c4d3872bd
commit 57ac55c53f
25 changed files with 840 additions and 368 deletions

View File

@@ -508,7 +508,7 @@ class DebianPackageManager(SoftwareManager, SettingsController):
root_password=root_password, aptitude=self.aptitude)
sync_pkgs.start()
if DebianSuggestionsDownloader.should_download(deb_config, self._log, only_positive_exp=True):
if self.suggestions_downloader.should_download(deb_config, only_positive_exp=True):
self.suggestions_downloader.register_task(task_manager)
self.suggestions_downloader.start()
@@ -887,8 +887,13 @@ class DebianPackageManager(SoftwareManager, SettingsController):
@property
def suggestions_downloader(self) -> DebianSuggestionsDownloader:
if not self._suggestions_downloader:
file_url = self.context.get_suggestion_url(self.__module__)
self._suggestions_downloader = DebianSuggestionsDownloader(i18n=self._i18n, logger=self._log,
http_client=self.context.http_client)
http_client=self.context.http_client,
file_url=file_url)
if self._suggestions_downloader.is_local_suggestions_file():
self._log.info(f"Local Debian suggestions file mapped: {file_url}")
return self._suggestions_downloader

View File

@@ -9,6 +9,7 @@ from typing import Optional, Dict
from bauh.api.abstract.handler import TaskManager
from bauh.api.abstract.model import SuggestionPriority
from bauh.api.http import HttpClient
from bauh.commons.suggestions import parse
from bauh.gems.debian import DEBIAN_ICON_PATH, DEBIAN_CACHE_DIR
from bauh.view.util.translation import I18n
@@ -33,20 +34,18 @@ class DebianSuggestionsDownloader(Thread):
return cls._file_suggestions_ts
@classmethod
def url_suggestions(cls) -> str:
if cls._url_suggestions is None:
cls._url_suggestions = 'https://raw.githubusercontent.com/vinifmor/bauh-files' \
'/master/debian/suggestions.txt'
return cls._url_suggestions
def __init__(self, logger: Logger, http_client: HttpClient, i18n: I18n):
def __init__(self, logger: Logger, http_client: HttpClient, i18n: I18n, file_url: Optional[str]):
super(DebianSuggestionsDownloader, self).__init__()
self._log = logger
self.i18n = i18n
self.http_client = http_client
self._taskman: Optional[TaskManager] = None
if file_url:
self._file_url = file_url
else:
self._file_url = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/debian/suggestions_v1.txt'
self.task_id = 'debian.suggs'
def register_task(self, taskman: Optional[TaskManager]):
@@ -55,6 +54,9 @@ class DebianSuggestionsDownloader(Thread):
self._taskman.register_task(id_=self.task_id, label=self.i18n['task.download_suggestions'],
icon_path=DEBIAN_ICON_PATH)
def is_local_suggestions_file(self) -> bool:
return self._file_url and self._file_url.startswith('/')
@property
def taskman(self) -> TaskManager:
if self._taskman is None:
@@ -62,34 +64,40 @@ class DebianSuggestionsDownloader(Thread):
return self._taskman
@classmethod
def should_download(cls, debian_config: dict, logger: Logger, only_positive_exp: bool = False) -> bool:
def should_download(self, debian_config: dict, only_positive_exp: bool = False) -> bool:
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return False
if self.is_local_suggestions_file():
return False
try:
exp_hours = int(debian_config['suggestions.exp'])
except ValueError:
logger.error(f"The Debian configuration property 'suggestions.expiration' has a non int value set: "
f"{debian_config['suggestions']['expiration']}")
self._log.error(f"The Debian configuration property 'suggestions.expiration' has a non int value set: "
f"{debian_config['suggestions']['expiration']}")
return not only_positive_exp
if exp_hours <= 0:
logger.info("Suggestions cache is disabled")
self._log.info("Suggestions cache is disabled")
return not only_positive_exp
if not os.path.exists(cls.file_suggestions()):
logger.info(f"'{cls.file_suggestions()}' not found. It must be downloaded")
if not os.path.exists(self.file_suggestions()):
self._log.info(f"'{self.file_suggestions()}' not found. It must be downloaded")
return True
if not os.path.exists(cls.file_suggestions()):
logger.info(f"'{cls.file_suggestions()}' not found. The suggestions file must be downloaded.")
if not os.path.exists(self.file_suggestions()):
self._log.info(f"'{self.file_suggestions()}' not found. The suggestions file must be downloaded.")
return True
with open(cls.file_suggestions_timestamp()) as f:
with open(self.file_suggestions_timestamp()) as f:
timestamp_str = f.read()
try:
suggestions_timestamp = datetime.fromtimestamp(float(timestamp_str))
except:
logger.error(f'Could not parse the Debian cached suggestions timestamp: {timestamp_str}')
self._log.error(f'Could not parse the Debian cached suggestions timestamp: {timestamp_str}')
traceback.print_exc()
return True
@@ -97,6 +105,13 @@ class DebianSuggestionsDownloader(Thread):
return update
def _save(self, text: str, timestamp: float):
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return
if self.is_local_suggestions_file():
return False
self._log.info(f"Caching suggestions to '{self.file_suggestions()}'")
cache_dir = os.path.dirname(self.file_suggestions())
@@ -124,51 +139,49 @@ class DebianSuggestionsDownloader(Thread):
self._log.error(f"An exception happened while writing the file '{self.file_suggestions_timestamp()}'")
traceback.print_exc()
def parse_suggestions(self, suggestions_str: str) -> Dict[str, SuggestionPriority]:
output = dict()
for line in suggestions_str.split('\n'):
clean_line = line.strip()
if clean_line:
line_split = clean_line.split(':', 1)
if len(line_split) == 2:
try:
prio = int(line_split[0])
except ValueError:
self._log.warning(f"Could not parse Debian package suggestion: {line}")
continue
output[line_split[1]] = SuggestionPriority(prio)
return output
def read_cached(self) -> Optional[Dict[str, SuggestionPriority]]:
self._log.info(f"Reading cached suggestions file '{self.file_suggestions()}'")
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return
if self.is_local_suggestions_file():
file_path, log_ref = self._file_url, 'local'
else:
file_path, log_ref = self.file_suggestions(), 'cached'
self._log.info(f"Reading {log_ref} suggestions file {file_path}")
try:
with open(self.file_suggestions()) as f:
with open(file_path) as f:
sugs_str = f.read()
except FileNotFoundError:
self._log.warning(f"Cached suggestions file does not exist ({self.file_suggestions()})")
self._log.warning(f"The {log_ref} suggestions file does not exist ({file_path})")
return
except OSError:
self._log.warning(f"Could not read from the {log_ref} suggestions file ({file_path})")
traceback.print_exc()
return
if not sugs_str:
self._log.warning(f"Cached suggestions file '{self.file_suggestions()}' is empty")
self._log.warning(f"The {log_ref} suggestions file '{file_path}' is empty")
return
return self.parse_suggestions(sugs_str)
return parse(sugs_str, self._log, 'Debian')
def download(self) -> Optional[Dict[str, SuggestionPriority]]:
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return
self.taskman.update_progress(self.task_id, progress=1, substatus=None)
self._log.info(f"Downloading suggestions from {self.url_suggestions()}")
res = self.http_client.get(self.url_suggestions())
self._log.info(f"Downloading Debian suggestions from {self._file_url}")
res = self.http_client.get(self._file_url)
suggestions = None
if res.status_code == 200 and res.text:
self.taskman.update_progress(self.task_id, progress=50, substatus=None)
suggestions = self.parse_suggestions(res.text)
suggestions = parse(res.text, self._log, 'Debian')
if suggestions:
self._save(text=res.text, timestamp=datetime.utcnow().timestamp())
@@ -183,10 +196,14 @@ class DebianSuggestionsDownloader(Thread):
return suggestions
def read(self, debian_config: dict) -> Optional[Dict[str, int]]:
if self.should_download(debian_config=debian_config, logger=self._log):
return self.download()
if not self._file_url:
self._log.error("No Debian suggestions file URL defined")
return
return self.read_cached()
if self.is_local_suggestions_file() or not self.should_download(debian_config=debian_config):
return self.read_cached()
return self.download()
def run(self):
self.download()