|
|
|
@ -1,5 +1,7 @@
|
|
|
|
|
import hashlib
|
|
|
|
|
import threading
|
|
|
|
|
import traceback
|
|
|
|
|
import watchfiles
|
|
|
|
|
|
|
|
|
|
from configparser import ConfigParser
|
|
|
|
|
from izzylib.logging import LogLevel
|
|
|
|
@ -8,12 +10,10 @@ from izzylib.object_base import ObjectBase
|
|
|
|
|
from izzylib.path import Path
|
|
|
|
|
from izzylib.url import Url
|
|
|
|
|
from izzylib_http_async import Template
|
|
|
|
|
from watchdog.events import FileSystemEventHandler
|
|
|
|
|
from watchdog.observers import Observer
|
|
|
|
|
|
|
|
|
|
from . import var, __version__ as version, __software__ as swname
|
|
|
|
|
from .base import ComponentBase
|
|
|
|
|
from .enums import StylePriority
|
|
|
|
|
from .enums import StylePriority, WatcherChangeType
|
|
|
|
|
from .functions import run_in_gui_thread
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -204,14 +204,8 @@ class Themes(ComponentBase, ObjectBase):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def watcher_start(self):
|
|
|
|
|
if self.watcher:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self.watcher = Observer()
|
|
|
|
|
|
|
|
|
|
self.watcher.schedule(MainWatchHandler(self), self.app.path.resources, recursive=False)
|
|
|
|
|
self.watcher.schedule(SystemWatchHandler(self), self.systempath, recursive=True)
|
|
|
|
|
self.watcher.schedule(UserWatchHandler(self), self.userpath, recursive=True)
|
|
|
|
|
if not self.watcher:
|
|
|
|
|
self.watcher = ThemeWatcher(self)
|
|
|
|
|
|
|
|
|
|
self.watcher.start()
|
|
|
|
|
|
|
|
|
@ -221,10 +215,6 @@ class Themes(ComponentBase, ObjectBase):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self.watcher.stop()
|
|
|
|
|
self.watcher.join()
|
|
|
|
|
|
|
|
|
|
self.watcher = None
|
|
|
|
|
self.current = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Theme(ObjectBase):
|
|
|
|
@ -310,10 +300,12 @@ class Theme(ObjectBase):
|
|
|
|
|
logging.error(f'[{self.name}] {error.message} {section.get_start_line()}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WatcherBase(ComponentBase, FileSystemEventHandler):
|
|
|
|
|
class ThemeWatcher(ComponentBase):
|
|
|
|
|
def __init__(self, themes):
|
|
|
|
|
FileSystemEventHandler.__init__(self)
|
|
|
|
|
self.themes = themes
|
|
|
|
|
self._stop = threading.Event()
|
|
|
|
|
self._stopped = threading.Event()
|
|
|
|
|
self._stopped.set()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@ -331,84 +323,103 @@ class WatcherBase(ComponentBase, FileSystemEventHandler):
|
|
|
|
|
return self.themes.userpath
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
|
if not self._stopped.is_set():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
thread = threading.Thread(target=self.handle_file_watcher)
|
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
|
|
logging.verbose('Started theme watcher')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
|
if self._stopped.is_set():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self._stop.set()
|
|
|
|
|
self._stopped.wait()
|
|
|
|
|
|
|
|
|
|
logging.verbose('Stopped theme watcher')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_file_watcher(self):
|
|
|
|
|
self._stopped.clear()
|
|
|
|
|
|
|
|
|
|
watcher = watchfiles.watch(
|
|
|
|
|
self.path.resources,
|
|
|
|
|
self.systempath,
|
|
|
|
|
self.userpath,
|
|
|
|
|
recursive = True,
|
|
|
|
|
force_polling = True,
|
|
|
|
|
poll_delay_ms = 250,
|
|
|
|
|
stop_event = self._stop
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for changes in watcher:
|
|
|
|
|
for change in changes:
|
|
|
|
|
try:
|
|
|
|
|
action = change[0]
|
|
|
|
|
path = Path(change[1])
|
|
|
|
|
|
|
|
|
|
except IndexError:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if path.startswith(self.app.path.resources):
|
|
|
|
|
if path.name == 'main.css':
|
|
|
|
|
run_in_gui_thread(self.themes.load_main)
|
|
|
|
|
|
|
|
|
|
elif any(map(path.startswith, [self.themes.systempath, self.themes.userpath])):
|
|
|
|
|
system = path.startswith(self.themes.systempath)
|
|
|
|
|
|
|
|
|
|
try: theme = self.themes.get_theme_by_file(path, system=systempath)
|
|
|
|
|
except KeyError: theme = None
|
|
|
|
|
|
|
|
|
|
if theme and action == WatcherChangeType.MODIFY:
|
|
|
|
|
run_in_gui_thread(self.handle_theme_modified, theme, path)
|
|
|
|
|
|
|
|
|
|
elif theme and action == WatcherChangeType.DELETE:
|
|
|
|
|
if path.name == 'manifest.ini':
|
|
|
|
|
run_in_gui_thread(self.handle_theme_deleted, theme, path)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
run_in_gui_thread(self.handle_theme_modified, theme, path)
|
|
|
|
|
|
|
|
|
|
elif action == WatcherChangeType.CREATE:
|
|
|
|
|
run_in_gui_thread(self.handle_theme_created, path)
|
|
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
self._stopped.set()
|
|
|
|
|
self._stop.clear()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_theme_created(self, path):
|
|
|
|
|
if not (path.name == 'manifest.ini' and self.userpath.join(path.parent.name).isdir()):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
theme = Theme(path.parent)
|
|
|
|
|
self.themes.user[theme.hash] = theme
|
|
|
|
|
|
|
|
|
|
logging.verbose(f'Created new theme: {theme.name}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_theme_deleted(self, theme, path):
|
|
|
|
|
if self.themes.current == theme.hash:
|
|
|
|
|
self.themes.unset()
|
|
|
|
|
|
|
|
|
|
del self.themes.user[theme.hash]
|
|
|
|
|
|
|
|
|
|
logging.verbose(f'Deleted theme: {theme.name}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_theme_modified(self, theme, path):
|
|
|
|
|
try:
|
|
|
|
|
if path.name == 'manifest.ini':
|
|
|
|
|
theme.load_manifest()
|
|
|
|
|
logging.verbose(f'Reloaded manifest for theme: {theme.name}')
|
|
|
|
|
|
|
|
|
|
elif path.suffix in ['css', 'svg', 'png', 'gif', 'jpg']:
|
|
|
|
|
theme.load()
|
|
|
|
|
logging.verbose(f'Reloaded theme: {theme.name}')
|
|
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MainWatchHandler(WatcherBase):
|
|
|
|
|
def on_modified(self, event):
|
|
|
|
|
path = Path(event.src_path)
|
|
|
|
|
|
|
|
|
|
if path.name == 'main.css':
|
|
|
|
|
logging.verbose('Reloading main css')
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
run_in_gui_thread(self.themes.load_main)
|
|
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SystemWatchHandler(WatcherBase):
|
|
|
|
|
def on_modified(self, event):
|
|
|
|
|
path = Path(event.src_path)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
theme = self.themes.get_theme_by_file(path, system=True)
|
|
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
run_in_gui_thread(self.handle_theme_modified, theme, path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserWatchHandler(WatcherBase):
|
|
|
|
|
def on_modified(self, event):
|
|
|
|
|
path = Path(event.src_path)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
theme = self.themes.get_theme_by_file(path)
|
|
|
|
|
|
|
|
|
|
except KeyError:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self.handle_theme_modified(theme, path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def on_deleted(self, event):
|
|
|
|
|
path = Path(event.src_path)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
theme = self.themes.get_theme_by_file(path)
|
|
|
|
|
|
|
|
|
|
except KeyError:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if path.name == 'manifest.ini':
|
|
|
|
|
if self.themes.current == theme.hash:
|
|
|
|
|
run_in_gui_thread(self.themes.unset)
|
|
|
|
|
theme.load_manifest()
|
|
|
|
|
logging.verbose(f'Reloaded manifest for theme: {theme.name}')
|
|
|
|
|
|
|
|
|
|
del self.themes.user[theme.hash]
|
|
|
|
|
|
|
|
|
|
logging.verbose(f'Deleted theme: {theme.name}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def on_created(self, event):
|
|
|
|
|
path = Path(event.src_path)
|
|
|
|
|
|
|
|
|
|
if path.name == 'manifest.ini' and self.userpath.join(path.parent).isdir():
|
|
|
|
|
theme = Theme(path.parent)
|
|
|
|
|
self.themes.user[theme.hash] = theme
|
|
|
|
|
|
|
|
|
|
logging.verbose(f'Created new theme: {theme.name}')
|
|
|
|
|
else:
|
|
|
|
|
theme.load()
|
|
|
|
|
logging.verbose(f'Reloaded theme: {theme.name}')
|
|
|
|
|