mirror of
https://github.com/stashapp/CommunityScripts.git
synced 2025-12-13 12:36:10 -06:00
161 lines
5.1 KiB
Python
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)
|
|
|
|
|
|
|
|
|
|
|