mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 00:04:15 +02:00
refactoring modules structure | improving README and CHANGELOG
This commit is contained in:
0
bauh/view/util/__init__.py
Normal file
0
bauh/view/util/__init__.py
Normal file
107
bauh/view/util/cache.py
Normal file
107
bauh/view/util/cache.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import datetime
|
||||
import time
|
||||
from threading import Lock, Thread
|
||||
|
||||
from bauh.api.abstract.cache import MemoryCache, MemoryCacheFactory
|
||||
|
||||
|
||||
class DefaultMemoryCache(MemoryCache):
|
||||
"""
|
||||
A synchronized cache implementation
|
||||
"""
|
||||
|
||||
def __init__(self, expiration_time: int):
|
||||
super(DefaultMemoryCache, self).__init__()
|
||||
self.expiration_time = expiration_time
|
||||
self._cache = {}
|
||||
self.lock = Lock()
|
||||
|
||||
def is_enabled(self):
|
||||
return self.expiration_time < 0 or self.expiration_time > 0
|
||||
|
||||
def add(self, key: str, val: object):
|
||||
if key and self.is_enabled():
|
||||
self.lock.acquire()
|
||||
self._add(key, val)
|
||||
self.lock.release()
|
||||
|
||||
def _add(self, key: str, val: object):
|
||||
if key:
|
||||
self._cache[key] = {'val': val, 'expires_at': datetime.datetime.utcnow() + datetime.timedelta(seconds=self.expiration_time) if self.expiration_time > 0 else None}
|
||||
|
||||
def add_non_existing(self, key: str, val: object):
|
||||
if key and self. is_enabled():
|
||||
self.lock.acquire()
|
||||
cur_val = self.get(key)
|
||||
|
||||
if cur_val is None:
|
||||
self._add(key, val)
|
||||
|
||||
self.lock.release()
|
||||
|
||||
def get(self, key: str):
|
||||
if key and self.is_enabled():
|
||||
val = self._cache.get(key)
|
||||
|
||||
if val:
|
||||
expiration = val.get('expires_at')
|
||||
|
||||
if expiration and expiration <= datetime.datetime.utcnow():
|
||||
self.lock.acquire()
|
||||
del self._cache[key]
|
||||
self.lock.release()
|
||||
return None
|
||||
|
||||
return val['val']
|
||||
|
||||
def delete(self, key):
|
||||
if key and self.is_enabled():
|
||||
if key in self._cache:
|
||||
self.lock.acquire()
|
||||
del self._cache[key]
|
||||
self.lock.release()
|
||||
|
||||
def keys(self):
|
||||
return set(self._cache.keys()) if self.is_enabled() else set()
|
||||
|
||||
def clean_expired(self):
|
||||
if self.is_enabled():
|
||||
for key in self.keys():
|
||||
self.get(key)
|
||||
|
||||
|
||||
class CacheCleaner(Thread):
|
||||
|
||||
def __init__(self, check_interval: int = 15):
|
||||
super(CacheCleaner, self).__init__(daemon=True)
|
||||
self.caches = []
|
||||
self.check_interval = check_interval
|
||||
|
||||
def register(self, cache: MemoryCache):
|
||||
if cache.is_enabled():
|
||||
self.caches.append(cache)
|
||||
|
||||
def run(self):
|
||||
if self.caches:
|
||||
while True:
|
||||
for cache in self.caches:
|
||||
cache.clean_expired()
|
||||
|
||||
time.sleep(self.check_interval)
|
||||
|
||||
|
||||
class DefaultMemoryCacheFactory(MemoryCacheFactory):
|
||||
|
||||
def __init__(self, expiration_time: int, cleaner: CacheCleaner):
|
||||
"""
|
||||
:param expiration_time: default expiration time for all instantiated caches
|
||||
:param cleaner
|
||||
"""
|
||||
super(DefaultMemoryCacheFactory, self).__init__()
|
||||
self.expiration_time = expiration_time
|
||||
self.cleaner = cleaner
|
||||
|
||||
def new(self, expiration: int = None) -> MemoryCache:
|
||||
instance = DefaultMemoryCache(expiration if expiration is not None else self.expiration_time)
|
||||
self.cleaner.register(instance)
|
||||
return instance
|
||||
82
bauh/view/util/disk.py
Normal file
82
bauh/view/util/disk.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from threading import Thread, Lock
|
||||
from typing import Type, Dict
|
||||
|
||||
from bauh.api.abstract.cache import MemoryCache
|
||||
from bauh.api.abstract.disk import DiskCacheLoader, DiskCacheLoaderFactory
|
||||
from bauh.api.abstract.model import SoftwarePackage
|
||||
|
||||
|
||||
class AsyncDiskCacheLoader(Thread, DiskCacheLoader):
|
||||
|
||||
def __init__(self, enabled: bool, cache_map: Dict[Type[SoftwarePackage], MemoryCache], logger: logging.Logger):
|
||||
super(AsyncDiskCacheLoader, self).__init__(daemon=True)
|
||||
self.pkgs = []
|
||||
self._work = True
|
||||
self.lock = Lock()
|
||||
self.cache_map = cache_map
|
||||
self.enabled = enabled
|
||||
self.logger = logger
|
||||
self.processed = 0
|
||||
|
||||
def fill(self, pkg: SoftwarePackage):
|
||||
"""
|
||||
Adds a package which data must be read from the disk to a queue.
|
||||
:param pkg:
|
||||
:return:
|
||||
"""
|
||||
if self.enabled and pkg and pkg.supports_disk_cache():
|
||||
self.pkgs.append(pkg)
|
||||
|
||||
def stop_working(self):
|
||||
self._work = False
|
||||
|
||||
def run(self):
|
||||
if self.enabled:
|
||||
last = 0
|
||||
|
||||
while True:
|
||||
time.sleep(0.00001)
|
||||
if len(self.pkgs) > self.processed:
|
||||
pkg = self.pkgs[last]
|
||||
|
||||
self._fill_cached_data(pkg)
|
||||
self.processed += 1
|
||||
last += 1
|
||||
elif not self._work:
|
||||
break
|
||||
|
||||
def _fill_cached_data(self, pkg: SoftwarePackage) -> bool:
|
||||
if self.enabled:
|
||||
if os.path.exists(pkg.get_disk_data_path()):
|
||||
with open(pkg.get_disk_data_path()) as f:
|
||||
cached_data = json.loads(f.read())
|
||||
if cached_data:
|
||||
pkg.fill_cached_data(cached_data)
|
||||
cache = self.cache_map.get(pkg.__class__)\
|
||||
|
||||
if cache:
|
||||
cache.add_non_existing(pkg.id, cached_data)
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class DefaultDiskCacheLoaderFactory(DiskCacheLoaderFactory):
|
||||
|
||||
def __init__(self, disk_cache_enabled: bool, logger: logging.Logger):
|
||||
super(DefaultDiskCacheLoaderFactory, self).__init__()
|
||||
self.disk_cache_enabled = disk_cache_enabled
|
||||
self.logger = logger
|
||||
self.cache_map = {}
|
||||
|
||||
def map(self, pkg_type: Type[SoftwarePackage], cache: MemoryCache):
|
||||
if pkg_type:
|
||||
if pkg_type not in self.cache_map:
|
||||
self.cache_map[pkg_type] = cache
|
||||
|
||||
def new(self) -> AsyncDiskCacheLoader:
|
||||
return AsyncDiskCacheLoader(enabled=self.disk_cache_enabled, cache_map=self.cache_map, logger=self.logger)
|
||||
22
bauh/view/util/logs.py
Normal file
22
bauh/view/util/logs.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import logging
|
||||
from logging import INFO
|
||||
|
||||
FORMAT = '%(asctime)s %(levelname)s [%(module_path)s:%(lineno)s - %(funcName)s()] - %(message)s'
|
||||
|
||||
|
||||
class FilePathFilter(logging.Filter):
|
||||
|
||||
def filter(self, record):
|
||||
record.module_path = record.pathname.split('site-packages/')[1] if 'site-packages' in record.pathname else str(record.pathname)
|
||||
return True
|
||||
|
||||
|
||||
def new_logger(name: str, enabled: bool) -> logging.Logger:
|
||||
instance = logging.Logger(name, level=INFO)
|
||||
instance.addFilter(FilePathFilter())
|
||||
stream_handler = logging.StreamHandler()
|
||||
stream_handler.setFormatter(logging.Formatter(FORMAT))
|
||||
instance.addHandler(stream_handler)
|
||||
instance.disabled = not enabled
|
||||
|
||||
return instance
|
||||
5
bauh/view/util/resource.py
Executable file
5
bauh/view/util/resource.py
Executable file
@@ -0,0 +1,5 @@
|
||||
from bauh import ROOT_DIR
|
||||
|
||||
|
||||
def get_path(resource_path):
|
||||
return ROOT_DIR + '/view/resources/' + resource_path
|
||||
63
bauh/view/util/util.py
Normal file
63
bauh/view/util/util.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import glob
|
||||
import locale
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from bauh import __app_name__
|
||||
from bauh.view.util import resource
|
||||
|
||||
|
||||
def get_locale_keys(key: str = None, locale_dir: str = resource.get_path('locale')):
|
||||
|
||||
locale_path = None
|
||||
|
||||
if key is None:
|
||||
current_locale = locale.getdefaultlocale()
|
||||
else:
|
||||
current_locale = [key.strip().lower()]
|
||||
|
||||
if current_locale:
|
||||
current_locale = current_locale[0]
|
||||
|
||||
for locale_file in glob.glob(locale_dir + '/*'):
|
||||
name = locale_file.split('/')[-1]
|
||||
|
||||
if current_locale == name or current_locale.startswith(name + '_'):
|
||||
locale_path = locale_file
|
||||
break
|
||||
|
||||
if not locale_path:
|
||||
locale_path = resource.get_path('locale/en')
|
||||
|
||||
with open(locale_path, 'r') as f:
|
||||
locale_keys = f.readlines()
|
||||
|
||||
locale_obj = {}
|
||||
for line in locale_keys:
|
||||
if line:
|
||||
keyval = line.strip().split('=')
|
||||
locale_obj[keyval[0].strip()] = keyval[1].strip()
|
||||
|
||||
return locale_obj
|
||||
|
||||
|
||||
def notify_user(msg: str, icon_path: str = resource.get_path('img/logo.svg')):
|
||||
os.system("notify-send -a {} {} '{}'".format(__app_name__, "-i {}".format(icon_path) if icon_path else '', msg))
|
||||
|
||||
|
||||
def restart_app(show_panel: bool):
|
||||
"""
|
||||
:param show_panel: if the panel should be displayed after the app restart
|
||||
:return:
|
||||
"""
|
||||
restart_cmd = [sys.executable, *sys.argv]
|
||||
|
||||
if show_panel:
|
||||
restart_cmd.append('--show-panel')
|
||||
|
||||
subprocess.Popen(restart_cmd)
|
||||
QCoreApplication.exit()
|
||||
|
||||
Reference in New Issue
Block a user