2023-09-26 17:21:33 -07:00

161 lines
5.1 KiB
Python

#!/usr/bin/python -w
import argparse
import configparser
import time
import os
from threading import Lock, Condition
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
from stashapi.stashapp import StashInterface
import logging
#Setup logger
logger = logging.getLogger("stash-watcher")
logger.setLevel(logging.INFO)
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter("%(asctime)s %(message)s"))
logger.addHandler(ch)
#This signals that we should
shouldUpdate = False
mutex = Lock()
signal = Condition(mutex)
modifiedFiles = {}
def log(msg):
logger.info(msg)
def debug(msg):
logger.debug(msg)
def handleEvent(event):
global shouldUpdate
debug("========EVENT========")
debug(str(event))
#log(modifiedFiles)
#Record if the file was modified. When a file is closed, see if it was modified. If so, trigger
shouldTrigger = False
if event.is_directory == False:
if event.event_type == "modified":
modifiedFiles[event.src_path] = 1
#These are for files being copied into the target
elif event.event_type == "closed":
if event.src_path in modifiedFiles:
del modifiedFiles[event.src_path]
shouldTrigger = True
#For download managers and the like that write to a temporary file and then move to the destination (real)
#path. Note that this actually triggers if the destination is in the watched location, and not just if it's
#moved out of a watched directory
elif event.event_type == "moved":
shouldTrigger = True
#Trigger the update
if shouldTrigger:
debug("Triggering updates")
with mutex:
shouldUpdate = True
signal.notify()
def main(stash, scanFlags, paths, extensions, timeout):
global shouldUpdate
if len(extensions) == 1 and extensions[0] == "*":
patterns = ["*"]
else:
patterns = list(map(lambda x : "*." + x, extensions))
eventHandler = PatternMatchingEventHandler(patterns, None, False, True)
eventHandler.on_any_event = handleEvent
observer = Observer()
for path in paths:
observer.schedule(eventHandler, path, recursive=True)
observer.start()
try:
while True:
with mutex:
while not shouldUpdate:
signal.wait()
shouldUpdate = False
log("Triggering stash scan")
stash.metadata_scan(flags = scanFlags)
log("Sleeping for " + str(timeout) + " seconds")
time.sleep(timeout)
except KeyboardInterrupt:
observer.stop()
observer.join()
def listConverter(item):
debug("listConverter(" + str(item) + ")")
if not item:
return None
listItems = [i.strip() for i in item.split(',')]
if not listItems or (len(listItems) == 1 and not listItems[0]):
return None
return listItems
def makeArgParser():
parser = argparse.ArgumentParser(description='Stash file watcher')
parser.add_argument('config_path', nargs=1, help='Config file path (toml)')
return parser
def parseConfig(path):
config = configparser.ConfigParser(converters={'list': listConverter })
#Load the defaults first
defaults_path = os.path.join(os.path.dirname('__file__'), 'defaults.toml')
config.read(defaults_path)
#Now read the user config
config.read(path)
return config
if __name__ == '__main__':
#Parse the arguments
parser = makeArgParser()
args = parser.parse_args()
configPath = args.config_path
config = parseConfig(configPath)
#Set up Stash
stashArgs = {
"scheme": config["Host"]["Scheme"],
"host": config["Host"]["Host"],
"port": config["Host"]["Port"]
}
if config["Host"]["ApiKey"]:
stashArgs["ApiKey"] = config["Host"]["ApiKey"]
stash = StashInterface(stashArgs)
#And now the flags for the scan
scanFlags = {
"scanGenerateCovers": config["ScanOptions"].getboolean("Covers"),
"scanGeneratePreviews": config["ScanOptions"].getboolean("Previews"),
"scanGenerateImagePreviews": config["ScanOptions"].getboolean("ImagePreviews"),
"scanGenerateSprites": config["ScanOptions"].getboolean("Sprites"),
"scanGeneratePhashes": config["ScanOptions"].getboolean("Phashes"),
"scanGenerateThumbnails": config["ScanOptions"].getboolean("Thumbnails"),
"scanGenerateClipPreviews": config["ScanOptions"].getboolean("ClipPreviews")
}
paths = config.getlist("Config", "Paths")
timeout = config["Config"].getint("Cooldown")
#If the extensions are in the config, use them. Otherwise pull them from stash.
extensions = config.getlist('Config', 'Extensions')
if not extensions:
stashConfig = stash.graphql_configuration()
extensions = stashConfig['general']['videoExtensions'] + stashConfig['general']['imageExtensions'] + stashConfig['general']['galleryExtensions']
main(stash, scanFlags, paths, extensions, timeout)