feat: support watching symlinks in watchdogg.py

+ remove code for auto-adding ~/Home to root_dirs during populate
This commit is contained in:
geoffrey45 2023-01-24 16:30:17 +03:00
parent 29e61b31c3
commit df6609e7f4
6 changed files with 131 additions and 79 deletions

View File

@ -30,8 +30,11 @@ def get_folder_tree():
root_dirs = db.get_root_dirs()
if req_dir == "$home" and root_dirs[0] == "$home":
req_dir = settings.USER_HOME_DIR
try:
if req_dir == "$home" and root_dirs[0] == "$home":
req_dir = settings.USER_HOME_DIR
except IndexError:
pass
if req_dir == "$home":
folders = [Path(f) for f in root_dirs]

View File

@ -1,12 +1,13 @@
from flask import Blueprint, request
from app import settings
from app.db.sqlite.settings import SettingsSQLMethods as sdb
from app.lib import populate
from app.logger import log
from app.logger import log
from app.lib import populate
from app.db.store import Store
from app.utils import background
from app.lib.watchdogg import Watcher as WatchDog
from app.db.sqlite.settings import SettingsSQLMethods as sdb
api = Blueprint("settings", __name__, url_prefix="/")
@ -19,6 +20,10 @@ def get_child_dirs(parent: str, children: list[str]):
@background
def rebuild_store(db_dirs: list[str]):
"""
Restarts the watchdog and rebuilds the music library.
"""
log.info("Rebuilding library...")
Store.remove_tracks_by_dir_except(db_dirs)
@ -28,6 +33,7 @@ def rebuild_store(db_dirs: list[str]):
Store.load_artists()
populate.Populate()
WatchDog().restart()
log.info("Rebuilding library... ✅")
@ -59,13 +65,13 @@ def add_root_dirs():
db_dirs = sdb.get_root_dirs()
_h = "$home"
if db_dirs[0] == _h and new_dirs[0] == _h.strip():
return {"msg": "Not changed!"}
if db_dirs[0] == _h:
sdb.remove_root_dirs(db_dirs)
try:
if db_dirs[0] == _h and new_dirs[0] == _h.strip():
return {"msg": "Not changed!"}
if db_dirs[0] == _h:
sdb.remove_root_dirs(db_dirs)
if new_dirs[0] == _h:
finalize([_h], db_dirs, [settings.USER_HOME_DIR])

View File

@ -26,31 +26,16 @@ class Populate:
"""
def __init__(self) -> None:
text = {
"root_unset": "The root directory is not set. Trying to scan the default directory: %s",
"default_not_exists": "The directory: %s does not exist. Please open the app in your web browser to set the root directory.",
"no_tracks": "No tracks found in: %s. Please open the app in your web browser to set the root directory.",
}
tracks = get_all_tracks()
tracks = list(tracks)
dirs_to_scan = sdb.get_root_dirs()
initial_dirs_count = len(dirs_to_scan)
def_dir = "~/Music"
if len(dirs_to_scan) == 0:
log.warning(text["root_unset"], def_dir)
print("...")
exists = os.path.exists(settings.MUSIC_DIR)
if not exists:
log.warning(text["default_not_exists"], def_dir)
return
dirs_to_scan = [settings.MUSIC_DIR]
log.warning(
"The root directory is not configured. Open the app in your web browser to configure."
)
return
try:
if dirs_to_scan[0] == "$home":
@ -65,26 +50,6 @@ class Populate:
untagged = self.filter_untagged(tracks, files)
if initial_dirs_count == 0 and len(untagged) == 0:
log.warning(text["no_tracks"], def_dir)
return
if initial_dirs_count == 0 and len(untagged) > 0:
log.info(
"%sFound %s tracks 💪 %s",
settings.TCOLOR.OKGREEN,
len(untagged),
settings.TCOLOR.ENDC,
)
log.info(
"%s%s saved as the default root directory. 😶%s",
settings.TCOLOR.OKGREEN,
def_dir,
settings.TCOLOR.ENDC,
)
sdb.add_root_dirs(dirs_to_scan)
# return
if len(untagged) == 0:
log.info("All clear, no unread files.")
return

View File

@ -2,17 +2,22 @@
This library contains the classes and functions related to the watchdog file watcher.
"""
import os
import sqlite3
import time
from watchdog.events import PatternMatchingEventHandler
from watchdog.observers import Observer
from app.db.sqlite.tracks import SQLiteManager
from app.db.sqlite.tracks import SQLiteTrackMethods as db
from app.logger import log
from app.db.store import Store
from app.lib.taglib import get_tags
from app.logger import log
from app.models import Artist, Track
from app import settings
from app.db.sqlite.tracks import SQLiteManager
from app.db.sqlite.tracks import SQLiteTrackMethods as db
from app.db.sqlite.settings import SettingsSQLMethods as sdb
class Watcher:
@ -20,39 +25,96 @@ class Watcher:
Contains the methods for initializing and starting watchdog.
"""
home_dir = os.path.expanduser("~")
dirs = [home_dir]
observers: list[Observer] = []
def __init__(self):
self.observer = Observer()
def run(self):
event_handler = Handler()
"""
Starts watchers for each dir in root_dirs
"""
for dir_ in self.dirs:
trials = 0
while trials < 10:
try:
dirs = sdb.get_root_dirs()
print(dirs)
dir_map = [
{"original": d, "realpath": os.path.realpath(d)} for d in dirs
]
break
except sqlite3.OperationalError:
trials += 1
time.sleep(1)
else:
log.error(
"WatchDogError: Failed to start Watchdog. Waiting for database timed out!"
)
return
if len(dirs) == 0:
log.warning(
"WatchDogInfo: No root directories configured. Watchdog not started."
)
return
dir_map = [d for d in dir_map if d['realpath'] != d['original']]
if len(dirs) > 0 and dirs[0] == "$home":
dirs = [settings.USER_HOME_DIR]
event_handler = Handler(root_dirs=dirs, dir_map=dir_map)
for _dir in dirs:
exists = os.path.exists(_dir)
if not exists:
log.error("WatchdogError: Directory not found: %s", _dir)
for _dir in dirs:
self.observer.schedule(
event_handler, os.path.realpath(dir_), recursive=True
event_handler, os.path.realpath(_dir), recursive=True
)
self.observers.append(self.observer)
try:
self.observer.start()
except OSError:
log.error("Could not start watchdog.")
log.info("Started watchdog")
except FileNotFoundError:
log.error(
"WatchdogError: Failed to start watchdog, root directories could not be resolved."
)
return
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
for obsv in self.observers:
obsv.unschedule_all()
obsv.stop()
self.stop_all()
for obsv in self.observers:
obsv.join()
def stop_all(self):
"""
Unschedules and stops all existing watchers.
"""
log.info("Stopping all watchdog observers")
for obsv in self.observers:
obsv.unschedule_all()
obsv.stop()
def restart(self):
"""
Stops all existing watchers, refetches root_dirs from the db
and restarts the watchers.
"""
log.info("🔃 Restarting watchdog")
self.stop_all()
self.run()
def add_track(filepath: str) -> None:
"""
@ -118,9 +180,13 @@ def remove_track(filepath: str) -> None:
class Handler(PatternMatchingEventHandler):
files_to_process = []
root_dirs = []
dir_map = []
def __init__(self, root_dirs: list[str], dir_map: dict[str:str]):
self.root_dirs = root_dirs
self.dir_map = dir_map
def __init__(self):
log.info("✅ started watchdog")
PatternMatchingEventHandler.__init__(
self,
patterns=["*.flac", "*.mp3"],
@ -128,6 +194,16 @@ class Handler(PatternMatchingEventHandler):
case_sensitive=False,
)
def get_abs_path(self, path: str):
"""
Convert a realpath to a path relative to the matching root directory.
"""
for d in self.dir_map:
if d["realpath"] in path:
return path.replace(d["realpath"], d["original"])
return path
def on_created(self, event):
"""
Fired when a supported file is created.
@ -138,8 +214,8 @@ class Handler(PatternMatchingEventHandler):
"""
Fired when a delete event occurs on a supported file.
"""
remove_track(event.src_path)
path = self.get_abs_path(event.src_path)
remove_track(path)
def on_moved(self, event):
"""
@ -148,14 +224,19 @@ class Handler(PatternMatchingEventHandler):
trash = "share/Trash"
if trash in event.dest_path:
remove_track(event.src_path)
path = self.get_abs_path(event.src_path)
remove_track(path)
elif trash in event.src_path:
add_track(event.dest_path)
path = self.get_abs_path(event.dest_path)
add_track(path)
elif trash not in event.dest_path and trash not in event.src_path:
add_track(event.dest_path)
remove_track(event.src_path)
dest_path = self.get_abs_path(event.dest_path)
src_path = self.get_abs_path(event.src_path)
add_track(dest_path)
remove_track(src_path)
def on_closed(self, event):
"""
@ -164,9 +245,7 @@ class Handler(PatternMatchingEventHandler):
try:
self.files_to_process.remove(event.src_path)
if os.path.getsize(event.src_path) > 0:
add_track(event.src_path)
path = self.get_abs_path(event.src_path)
add_track(path)
except ValueError:
pass
# watcher = Watcher()

View File

@ -10,9 +10,9 @@ class CustomFormatter(logging.Formatter):
Custom log formatter
"""
grey = "\x1b[38;20m"
grey = "\033[92m"
yellow = "\x1b[33;20m"
red = "\x1b[31;20m"
red = "\033[41m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
# format = (
@ -45,5 +45,4 @@ handler.setLevel(logging.DEBUG)
handler.setFormatter(CustomFormatter())
log.addHandler(handler)
# copied from: https://stackoverflow.com/a/56944256:

View File

@ -105,7 +105,7 @@ class TCOLOR:
OKBLUE = "\033[94m"
OKCYAN = "\033[96m"
OKGREEN = "\033[92m"
WARNING = "\033[93m"
YELLOW = "\033[93m"
FAIL = "\033[91m"
ENDC = "\033[0m"
BOLD = "\033[1m"