forked from Github/Axter-Stash
Added function ExecutePythonScript
Added logic to the scheduler to be able to call other scripts and perform command line calls.
This commit is contained in:
2
plugins/.gitignore
vendored
2
plugins/.gitignore
vendored
@@ -34,6 +34,8 @@ renamefile_settings.cpython-310.pyc
|
|||||||
/DeleteMe
|
/DeleteMe
|
||||||
/ATestPlugin
|
/ATestPlugin
|
||||||
/FileMonitor/working
|
/FileMonitor/working
|
||||||
|
test_script_hello_world.py
|
||||||
|
MyDummyFileFrom_test_script_hello_world.txt
|
||||||
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# FileMonitor: Ver 0.7.7 (By David Maisonave)
|
# FileMonitor: Ver 0.7.8 (By David Maisonave)
|
||||||
FileMonitor is a [Stash](https://github.com/stashapp/stash) plugin with the following two main features:
|
FileMonitor is a [Stash](https://github.com/stashapp/stash) plugin with the following two main features:
|
||||||
- Updates Stash when any file changes occurs in the Stash library.
|
- Updates Stash when any file changes occurs in the Stash library.
|
||||||
- **Task Scheduler**: Runs scheduled task based on the scheduler configuration in filemonitor_config.py.
|
- **Task Scheduler**: Runs scheduled task based on the scheduler configuration in filemonitor_config.py.
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import stashapi.log as stashLog # stashapi.log by default for error and critical logging
|
import stashapi.log as stashLog # stashapi.log by default for error and critical logging
|
||||||
from stashapi.stashapp import StashInterface
|
from stashapi.stashapp import StashInterface
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
import inspect
|
import inspect, sys, os, pathlib, logging, json
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
import __main__
|
import __main__
|
||||||
|
|
||||||
# StashPluginHelper (By David Maisonave aka Axter)
|
# StashPluginHelper (By David Maisonave aka Axter)
|
||||||
@@ -61,7 +56,7 @@ class StashPluginHelper:
|
|||||||
STDIN_READ = None
|
STDIN_READ = None
|
||||||
FRAGMENT_SERVER = None
|
FRAGMENT_SERVER = None
|
||||||
logger = None
|
logger = None
|
||||||
traceOncePreviousHits = []
|
logLinePreviousHits = []
|
||||||
|
|
||||||
# Prefix message value
|
# Prefix message value
|
||||||
LEV_TRACE = "TRACE: "
|
LEV_TRACE = "TRACE: "
|
||||||
@@ -218,7 +213,7 @@ class StashPluginHelper:
|
|||||||
def Trace(self, logMsg = "", printTo = 0, logAlways = False, lineNo = -1):
|
def Trace(self, logMsg = "", printTo = 0, logAlways = False, lineNo = -1):
|
||||||
if printTo == 0: printTo = self.LOG_TO_FILE
|
if printTo == 0: printTo = self.LOG_TO_FILE
|
||||||
if lineNo == -1:
|
if lineNo == -1:
|
||||||
lineNo = lineNo = inspect.currentframe().f_back.f_lineno
|
lineNo = inspect.currentframe().f_back.f_lineno
|
||||||
logLev = logging.INFO if logAlways else logging.DEBUG
|
logLev = logging.INFO if logAlways else logging.DEBUG
|
||||||
if self.DEBUG_TRACING or logAlways:
|
if self.DEBUG_TRACING or logAlways:
|
||||||
if logMsg == "":
|
if logMsg == "":
|
||||||
@@ -227,28 +222,24 @@ class StashPluginHelper:
|
|||||||
|
|
||||||
# Log once per session. Only logs the first time called from a particular line number in the code.
|
# Log once per session. Only logs the first time called from a particular line number in the code.
|
||||||
def TraceOnce(self, logMsg = "", printTo = 0, logAlways = False):
|
def TraceOnce(self, logMsg = "", printTo = 0, logAlways = False):
|
||||||
if printTo == 0: printTo = self.LOG_TO_FILE
|
|
||||||
lineNo = inspect.currentframe().f_back.f_lineno
|
lineNo = inspect.currentframe().f_back.f_lineno
|
||||||
logLev = logging.INFO if logAlways else logging.DEBUG
|
|
||||||
if self.DEBUG_TRACING or logAlways:
|
if self.DEBUG_TRACING or logAlways:
|
||||||
FuncAndLineNo = f"{inspect.currentframe().f_back.f_code.co_name}:{lineNo}"
|
FuncAndLineNo = f"{inspect.currentframe().f_back.f_code.co_name}:{lineNo}"
|
||||||
if FuncAndLineNo in self.traceOncePreviousHits:
|
if FuncAndLineNo in self.logLinePreviousHits:
|
||||||
return
|
return
|
||||||
self.traceOncePreviousHits.append(FuncAndLineNo)
|
self.logLinePreviousHits.append(FuncAndLineNo)
|
||||||
if logMsg == "":
|
self.Trace(logMsg, printTo, logAlways, lineNo)
|
||||||
logMsg = f"Line number {lineNo}..."
|
|
||||||
self.Log(logMsg, printTo, logLev, lineNo)
|
|
||||||
|
|
||||||
# Log INFO on first call, then do Trace on remaining calls.
|
# Log INFO on first call, then do Trace on remaining calls.
|
||||||
def LogOnce(self, logMsg = "", printTo = 0, logAlways = False, traceOnRemainingCalls = True):
|
def LogOnce(self, logMsg = "", printTo = 0, logAlways = False, traceOnRemainingCalls = True):
|
||||||
if printTo == 0: printTo = self.LOG_TO_FILE
|
if printTo == 0: printTo = self.LOG_TO_FILE
|
||||||
lineNo = inspect.currentframe().f_back.f_lineno
|
lineNo = inspect.currentframe().f_back.f_lineno
|
||||||
FuncAndLineNo = f"{inspect.currentframe().f_back.f_code.co_name}:{lineNo}"
|
FuncAndLineNo = f"{inspect.currentframe().f_back.f_code.co_name}:{lineNo}"
|
||||||
if FuncAndLineNo in self.traceOncePreviousHits:
|
if FuncAndLineNo in self.logLinePreviousHits:
|
||||||
if traceOnRemainingCalls:
|
if traceOnRemainingCalls:
|
||||||
self.Trace(logMsg, printTo, logAlways, lineNo)
|
self.Trace(logMsg, printTo, logAlways, lineNo)
|
||||||
else:
|
else:
|
||||||
self.traceOncePreviousHits.append(FuncAndLineNo)
|
self.logLinePreviousHits.append(FuncAndLineNo)
|
||||||
self.Log(logMsg, printTo, logging.INFO, lineNo)
|
self.Log(logMsg, printTo, logging.INFO, lineNo)
|
||||||
|
|
||||||
def Warn(self, logMsg, printTo = 0):
|
def Warn(self, logMsg, printTo = 0):
|
||||||
@@ -268,6 +259,26 @@ class StashPluginHelper:
|
|||||||
self.Log(f"StashPluginHelper Status: (CALLED_AS_STASH_PLUGIN={self.CALLED_AS_STASH_PLUGIN}), (RUNNING_IN_COMMAND_LINE_MODE={self.RUNNING_IN_COMMAND_LINE_MODE}), (DEBUG_TRACING={self.DEBUG_TRACING}), (DRY_RUN={self.DRY_RUN}), (PLUGIN_ID={self.PLUGIN_ID}), (PLUGIN_TASK_NAME={self.PLUGIN_TASK_NAME}), (STASH_URL={self.STASH_URL}), (MAIN_SCRIPT_NAME={self.MAIN_SCRIPT_NAME})",
|
self.Log(f"StashPluginHelper Status: (CALLED_AS_STASH_PLUGIN={self.CALLED_AS_STASH_PLUGIN}), (RUNNING_IN_COMMAND_LINE_MODE={self.RUNNING_IN_COMMAND_LINE_MODE}), (DEBUG_TRACING={self.DEBUG_TRACING}), (DRY_RUN={self.DRY_RUN}), (PLUGIN_ID={self.PLUGIN_ID}), (PLUGIN_TASK_NAME={self.PLUGIN_TASK_NAME}), (STASH_URL={self.STASH_URL}), (MAIN_SCRIPT_NAME={self.MAIN_SCRIPT_NAME})",
|
||||||
printTo, logLevel, lineNo)
|
printTo, logLevel, lineNo)
|
||||||
|
|
||||||
|
def ExecuteProcess(self, args):
|
||||||
|
import platform, subprocess
|
||||||
|
is_windows = any(platform.win32_ver())
|
||||||
|
pid = None
|
||||||
|
self.Trace(f"is_windows={is_windows} args={args}")
|
||||||
|
if is_windows:
|
||||||
|
self.Trace("Executing process using Windows DETACHED_PROCESS")
|
||||||
|
DETACHED_PROCESS = 0x00000008
|
||||||
|
pid = subprocess.Popen(args,creationflags=DETACHED_PROCESS, shell=True).pid
|
||||||
|
else:
|
||||||
|
self.Trace("Executing process using normal Popen")
|
||||||
|
pid = subprocess.Popen(args).pid
|
||||||
|
self.Trace(f"pid={pid}")
|
||||||
|
return pid
|
||||||
|
|
||||||
|
def ExecutePythonScript(self, args):
|
||||||
|
PythonExe = f"{sys.executable}"
|
||||||
|
argsWithPython = [f"{PythonExe}"] + args
|
||||||
|
return self.ExecuteProcess(argsWithPython)
|
||||||
|
|
||||||
# Extends class StashInterface with functions which are not yet in the class
|
# Extends class StashInterface with functions which are not yet in the class
|
||||||
class ExtendStashInterface(StashInterface):
|
class ExtendStashInterface(StashInterface):
|
||||||
def metadata_autotag(self, paths:list=[], dry_run=False):
|
def metadata_autotag(self, paths:list=[], dry_run=False):
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ if parse_args.quit:
|
|||||||
settings = {
|
settings = {
|
||||||
"recursiveDisabled": False,
|
"recursiveDisabled": False,
|
||||||
"turnOnScheduler": False,
|
"turnOnScheduler": False,
|
||||||
"zzdebugTracing": False,
|
"zmaximumBackups": 0,
|
||||||
"zzdryRun": False,
|
"zzdebugTracing": False
|
||||||
}
|
}
|
||||||
plugin = StashPluginHelper(
|
plugin = StashPluginHelper(
|
||||||
stash_url=parse_args.stash_url,
|
stash_url=parse_args.stash_url,
|
||||||
@@ -79,7 +79,7 @@ if plugin.DRY_RUN:
|
|||||||
plugin.Log("Dry run mode is enabled.")
|
plugin.Log("Dry run mode is enabled.")
|
||||||
plugin.Trace(f"(SCAN_MODIFIED={SCAN_MODIFIED}) (SCAN_ON_ANY_EVENT={SCAN_ON_ANY_EVENT}) (RECURSIVE={RECURSIVE})")
|
plugin.Trace(f"(SCAN_MODIFIED={SCAN_MODIFIED}) (SCAN_ON_ANY_EVENT={SCAN_ON_ANY_EVENT}) (RECURSIVE={RECURSIVE})")
|
||||||
|
|
||||||
StartFileMonitorAsAPluginTaskName = "Run as a Plugin"
|
StartFileMonitorAsAPluginTaskName = "Monitor as a Plugin"
|
||||||
StartFileMonitorAsAServiceTaskName = "Start Library Monitor Service"
|
StartFileMonitorAsAServiceTaskName = "Start Library Monitor Service"
|
||||||
FileMonitorPluginIsOnTaskQue = plugin.CALLED_AS_STASH_PLUGIN
|
FileMonitorPluginIsOnTaskQue = plugin.CALLED_AS_STASH_PLUGIN
|
||||||
StopLibraryMonitorWaitingInTaskQueue = False
|
StopLibraryMonitorWaitingInTaskQueue = False
|
||||||
@@ -105,88 +105,105 @@ def isJobWaitingToRun():
|
|||||||
|
|
||||||
if plugin.CALLED_AS_STASH_PLUGIN:
|
if plugin.CALLED_AS_STASH_PLUGIN:
|
||||||
plugin.Trace(f"isJobWaitingToRun() = {isJobWaitingToRun()})")
|
plugin.Trace(f"isJobWaitingToRun() = {isJobWaitingToRun()})")
|
||||||
|
|
||||||
def trimDbFiles(dbPath, maxFiles):
|
|
||||||
if not os.path.exists(dbPath) or len(dbPath) < 5: # For safety and security, short path not supported.
|
|
||||||
return
|
|
||||||
dbFiles = sorted(os.listdir(dbPath))
|
|
||||||
n = len(dbFiles)
|
|
||||||
for i in range(0, n-maxFiles):
|
|
||||||
dbFilePath = f"{dbPath}{os.sep}{dbFiles[i]}"
|
|
||||||
plugin.Log(f"Removing file {dbFilePath}")
|
|
||||||
os.remove(dbFilePath)
|
|
||||||
|
|
||||||
# Reoccurring scheduler code
|
class StashScheduler: # Stash Scheduler
|
||||||
# ToDo: Change the following functions into a class called reoccurringScheduler
|
def __init__(self):
|
||||||
def runTask(task):
|
import schedule # pip install schedule # https://github.com/dbader/schedule
|
||||||
import datetime
|
dayOfTheWeek = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
|
||||||
plugin.Trace(f"Running task {task}")
|
for task in plugin.pluginConfig['task_reoccurring_scheduler']:
|
||||||
if 'monthly' in task:
|
if 'hours' in task and task['hours'] > 0:
|
||||||
dayOfTheMonth = datetime.datetime.today().day
|
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' at {task['hours']} hours interval")
|
||||||
FirstAllowedDate = ((task['monthly'] - 1) * 7) + 1
|
schedule.every(task['hours']).hours.do(self.runTask, task)
|
||||||
LastAllowedDate = task['monthly'] * 7
|
elif 'minutes' in task and task['minutes'] > 0:
|
||||||
if dayOfTheMonth < FirstAllowedDate or dayOfTheMonth > LastAllowedDate:
|
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' at {task['minutes']} minutes interval")
|
||||||
plugin.Log(f"Skipping task {task['task']} because today is not the right {task['weekday']} of the month. Target range is between {FirstAllowedDate} and {LastAllowedDate}.")
|
schedule.every(task['minutes']).minutes.do(self.runTask, task)
|
||||||
|
elif 'days' in task and task['days'] > 0: # Left here for backward compatibility, but should use weekday logic instead.
|
||||||
|
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' at {task['days']} days interval")
|
||||||
|
schedule.every(task['days']).days.do(self.runTask, task)
|
||||||
|
elif 'weekday' in task and task['weekday'].lower() in dayOfTheWeek and 'time' in task:
|
||||||
|
if 'monthly' in task:
|
||||||
|
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' monthly on number {task['monthly']} {task['weekday']} at {task['time']}")
|
||||||
|
else:
|
||||||
|
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' (weekly) every {task['weekday']} at {task['time']}")
|
||||||
|
if task['weekday'].lower() == "monday":
|
||||||
|
schedule.every().monday.at(task['time']).do(self.runTask, task)
|
||||||
|
elif task['weekday'].lower() == "tuesday":
|
||||||
|
schedule.every().tuesday.at(task['time']).do(self.runTask, task)
|
||||||
|
elif task['weekday'].lower() == "wednesday":
|
||||||
|
schedule.every().wednesday.at(task['time']).do(self.runTask, task)
|
||||||
|
elif task['weekday'].lower() == "thursday":
|
||||||
|
schedule.every().thursday.at(task['time']).do(self.runTask, task)
|
||||||
|
elif task['weekday'].lower() == "friday":
|
||||||
|
schedule.every().friday.at(task['time']).do(self.runTask, task)
|
||||||
|
elif task['weekday'].lower() == "saturday":
|
||||||
|
schedule.every().saturday.at(task['time']).do(self.runTask, task)
|
||||||
|
elif task['weekday'].lower() == "sunday":
|
||||||
|
schedule.every().sunday.at(task['time']).do(self.runTask, task)
|
||||||
|
self.checkSchedulePending()
|
||||||
|
|
||||||
|
def runTask(self, task):
|
||||||
|
import datetime
|
||||||
|
plugin.Trace(f"Running task {task}")
|
||||||
|
if 'monthly' in task:
|
||||||
|
dayOfTheMonth = datetime.datetime.today().day
|
||||||
|
FirstAllowedDate = ((task['monthly'] - 1) * 7) + 1
|
||||||
|
LastAllowedDate = task['monthly'] * 7
|
||||||
|
if dayOfTheMonth < FirstAllowedDate or dayOfTheMonth > LastAllowedDate:
|
||||||
|
plugin.Log(f"Skipping task {task['task']} because today is not the right {task['weekday']} of the month. Target range is between {FirstAllowedDate} and {LastAllowedDate}.")
|
||||||
|
return
|
||||||
|
if task['task'] == "Clean":
|
||||||
|
plugin.STASH_INTERFACE.metadata_clean(paths=stashPaths, dry_run=plugin.DRY_RUN)
|
||||||
|
elif task['task'] == "Generate":
|
||||||
|
plugin.STASH_INTERFACE.metadata_generate()
|
||||||
|
elif task['task'] == "Backup":
|
||||||
|
plugin.LogOnce("Note: Backup task does not get listed in the Task Queue, but user can verify that it started by looking in the Stash log file as an INFO level log line.")
|
||||||
|
plugin.STASH_INTERFACE.backup_database()
|
||||||
|
if plugin.pluginSettings['zmaximumBackups'] > 1 and 'backupDirectoryPath' in plugin.STASH_CONFIGURATION:
|
||||||
|
if len(plugin.STASH_CONFIGURATION['backupDirectoryPath']) > 4 and os.path.exists(plugin.STASH_CONFIGURATION['backupDirectoryPath']):
|
||||||
|
plugin.LogOnce(f"Checking quantity of DB backups if path {plugin.STASH_CONFIGURATION['backupDirectoryPath']} exceeds {plugin.pluginSettings['zmaximumBackups']} backup files.")
|
||||||
|
self.trimDbFiles(plugin.STASH_CONFIGURATION['backupDirectoryPath'], plugin.pluginSettings['zmaximumBackups'])
|
||||||
|
elif task['task'] == "Scan":
|
||||||
|
plugin.STASH_INTERFACE.metadata_scan(paths=stashPaths)
|
||||||
|
elif task['task'] == "Auto Tag":
|
||||||
|
plugin.STASH_INTERFACE.metadata_autotag(paths=stashPaths, dry_run=plugin.DRY_RUN)
|
||||||
|
elif task['task'] == "Optimise Database":
|
||||||
|
plugin.STASH_INTERFACE.optimise_database()
|
||||||
|
elif task['task'] == "python":
|
||||||
|
script = task['script'].replace("<plugin_path>", f"{pathlib.Path(__file__).resolve().parent}{os.sep}")
|
||||||
|
plugin.Log(f"Executing python script {script}.")
|
||||||
|
args = [script]
|
||||||
|
if len(task['args']) > 0:
|
||||||
|
args = args + [task['args']]
|
||||||
|
plugin.ExecutePythonScript(args)
|
||||||
|
elif task['task'] == "execute":
|
||||||
|
cmd = task['command'].replace("<plugin_path>", f"{pathlib.Path(__file__).resolve().parent}{os.sep}")
|
||||||
|
plugin.Log(f"Executing command {cmd}.")
|
||||||
|
args = [cmd]
|
||||||
|
if len(task['args']) > 0:
|
||||||
|
args = args + [task['args']]
|
||||||
|
plugin.ExecuteProcess(args)
|
||||||
|
else:
|
||||||
|
# ToDo: Add code to check if plugin is installed.
|
||||||
|
plugin.Trace(f"Running plugin task pluginID={task['pluginId']}, task name = {task['task']}")
|
||||||
|
plugin.STASH_INTERFACE.run_plugin_task(plugin_id=task['pluginId'], task_name=task['task'])
|
||||||
|
|
||||||
|
def trimDbFiles(self, dbPath, maxFiles):
|
||||||
|
if not os.path.exists(dbPath):
|
||||||
|
plugin.LogOnce(f"Exiting trimDbFiles, because path {dbPath} does not exists.")
|
||||||
return
|
return
|
||||||
if task['task'] == "Clean":
|
if len(dbPath) < 5: # For safety and security, short path not supported.
|
||||||
plugin.STASH_INTERFACE.metadata_clean(paths=stashPaths, dry_run=plugin.DRY_RUN)
|
plugin.LogOnce(f"Exiting trimDbFiles, because path {dbPath} is to short. Len={len(dbPath)}. Path string must be at least 5 characters in length.")
|
||||||
elif task['task'] == "Generate":
|
return
|
||||||
plugin.STASH_INTERFACE.metadata_generate()
|
dbFiles = sorted(os.listdir(dbPath))
|
||||||
elif task['task'] == "Backup":
|
n = len(dbFiles)
|
||||||
plugin.LogOnce("Note: Backup task does not get listed in the Task Queue, but user can verify that it started by looking in the Stash log file as an INFO level log line.")
|
for i in range(0, n-maxFiles):
|
||||||
plugin.STASH_INTERFACE.backup_database()
|
dbFilePath = f"{dbPath}{os.sep}{dbFiles[i]}"
|
||||||
if plugin.pluginConfig['BackupsMax'] > 0 and plugin.pluginConfig['BackupDatabasePath'] != "" and os.path.exists(plugin.pluginConfig['BackupDatabasePath']):
|
plugin.Warn(f"Deleting file {dbFilePath}")
|
||||||
plugin.Log("Checking quantity of DB backups.")
|
os.remove(dbFilePath)
|
||||||
trimDbFiles(plugin.pluginConfig['BackupDatabasePath'], plugin.pluginConfig['BackupsMax'])
|
|
||||||
elif task['task'] == "Scan":
|
def checkSchedulePending(self):
|
||||||
plugin.STASH_INTERFACE.metadata_scan(paths=stashPaths)
|
import schedule # pip install schedule # https://github.com/dbader/schedule
|
||||||
elif task['task'] == "Auto Tag":
|
schedule.run_pending()
|
||||||
plugin.STASH_INTERFACE.metadata_autotag(paths=stashPaths, dry_run=plugin.DRY_RUN)
|
|
||||||
elif task['task'] == "Optimise Database":
|
|
||||||
plugin.STASH_INTERFACE.optimise_database()
|
|
||||||
else:
|
|
||||||
# ToDo: Add code to check if plugin is installed.
|
|
||||||
plugin.Trace(f"Running plugin task pluginID={task['pluginId']}, task name = {task['task']}")
|
|
||||||
plugin.STASH_INTERFACE.run_plugin_task(plugin_id=task['pluginId'], task_name=task['task'])
|
|
||||||
def reoccurringScheduler():
|
|
||||||
import schedule # pip install schedule # https://github.com/dbader/schedule
|
|
||||||
# ToDo: Extend schedule class so it works persistently (remember schedule between restarts)
|
|
||||||
# Or replace schedule with apscheduler https://github.com/agronholm/apscheduler
|
|
||||||
dayOfTheWeek = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
|
|
||||||
for task in plugin.pluginConfig['task_reoccurring_scheduler']:
|
|
||||||
if 'hours' in task and task['hours'] > 0:
|
|
||||||
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' at {task['hours']} hours interval")
|
|
||||||
schedule.every(task['hours']).hours.do(runTask, task)
|
|
||||||
elif 'minutes' in task and task['minutes'] > 0:
|
|
||||||
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' at {task['minutes']} minutes interval")
|
|
||||||
schedule.every(task['minutes']).minutes.do(runTask, task)
|
|
||||||
elif 'days' in task and task['days'] > 0:
|
|
||||||
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' at {task['days']} days interval")
|
|
||||||
schedule.every(task['days']).days.do(runTask, task)
|
|
||||||
elif 'weekday' in task and task['weekday'].lower() in dayOfTheWeek and 'time' in task:
|
|
||||||
if 'monthly' in task:
|
|
||||||
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' monthly on number {task['monthly']} {task['weekday']} at {task['time']}")
|
|
||||||
else:
|
|
||||||
plugin.Log(f"Adding to reoccurring scheduler task '{task['task']}' (weekly) every {task['weekday']} at {task['time']}")
|
|
||||||
if task['weekday'].lower() == "monday":
|
|
||||||
schedule.every().monday.at(task['time']).do(runTask, task)
|
|
||||||
elif task['weekday'].lower() == "tuesday":
|
|
||||||
schedule.every().tuesday.at(task['time']).do(runTask, task)
|
|
||||||
elif task['weekday'].lower() == "wednesday":
|
|
||||||
schedule.every().wednesday.at(task['time']).do(runTask, task)
|
|
||||||
elif task['weekday'].lower() == "thursday":
|
|
||||||
schedule.every().thursday.at(task['time']).do(runTask, task)
|
|
||||||
elif task['weekday'].lower() == "friday":
|
|
||||||
schedule.every().friday.at(task['time']).do(runTask, task)
|
|
||||||
elif task['weekday'].lower() == "saturday":
|
|
||||||
schedule.every().saturday.at(task['time']).do(runTask, task)
|
|
||||||
elif task['weekday'].lower() == "sunday":
|
|
||||||
schedule.every().sunday.at(task['time']).do(runTask, task)
|
|
||||||
def checkSchedulePending():
|
|
||||||
import schedule # pip install schedule # https://github.com/dbader/schedule
|
|
||||||
schedule.run_pending()
|
|
||||||
if plugin.pluginSettings['turnOnScheduler']:
|
|
||||||
reoccurringScheduler()
|
|
||||||
|
|
||||||
def start_library_monitor():
|
def start_library_monitor():
|
||||||
global shouldUpdate
|
global shouldUpdate
|
||||||
@@ -203,7 +220,7 @@ def start_library_monitor():
|
|||||||
shm_buffer[0] = CONTINUE_RUNNING_SIG
|
shm_buffer[0] = CONTINUE_RUNNING_SIG
|
||||||
plugin.Trace(f"Shared memory map opended, and flag set to {shm_buffer[0]}")
|
plugin.Trace(f"Shared memory map opended, and flag set to {shm_buffer[0]}")
|
||||||
RunCleanMetadata = False
|
RunCleanMetadata = False
|
||||||
|
stashScheduler = StashScheduler() if plugin.pluginSettings['turnOnScheduler'] else None
|
||||||
event_handler = watchdog.events.FileSystemEventHandler()
|
event_handler = watchdog.events.FileSystemEventHandler()
|
||||||
def on_created(event):
|
def on_created(event):
|
||||||
global shouldUpdate
|
global shouldUpdate
|
||||||
@@ -294,7 +311,7 @@ def start_library_monitor():
|
|||||||
plugin.Log(f"Breaking out of loop. (shm_buffer[0]={shm_buffer[0]})")
|
plugin.Log(f"Breaking out of loop. (shm_buffer[0]={shm_buffer[0]})")
|
||||||
break
|
break
|
||||||
if plugin.pluginSettings['turnOnScheduler']:
|
if plugin.pluginSettings['turnOnScheduler']:
|
||||||
checkSchedulePending()
|
stashScheduler.checkSchedulePending()
|
||||||
plugin.LogOnce("Waiting for a file change-trigger.")
|
plugin.LogOnce("Waiting for a file change-trigger.")
|
||||||
signal.wait(timeout=SIGNAL_TIMEOUT)
|
signal.wait(timeout=SIGNAL_TIMEOUT)
|
||||||
if plugin.pluginSettings['turnOnScheduler'] and not shouldUpdate:
|
if plugin.pluginSettings['turnOnScheduler'] and not shouldUpdate:
|
||||||
@@ -376,8 +393,6 @@ def stop_library_monitor():
|
|||||||
shm_a.unlink() # Call unlink only once to release the shared memory
|
shm_a.unlink() # Call unlink only once to release the shared memory
|
||||||
|
|
||||||
def start_library_monitor_service():
|
def start_library_monitor_service():
|
||||||
import subprocess
|
|
||||||
import platform
|
|
||||||
# First check if FileMonitor is already running
|
# First check if FileMonitor is already running
|
||||||
try:
|
try:
|
||||||
shm_a = shared_memory.SharedMemory(name=SHAREDMEMORY_NAME, create=False, size=4)
|
shm_a = shared_memory.SharedMemory(name=SHAREDMEMORY_NAME, create=False, size=4)
|
||||||
@@ -387,20 +402,9 @@ def start_library_monitor_service():
|
|||||||
return
|
return
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
plugin.Trace("FileMonitor is not running, so safe to start it as a service.")
|
plugin.Trace("FileMonitor is not running, so it's safe to start it as a service.")
|
||||||
is_windows = any(platform.win32_ver())
|
args = [f"{pathlib.Path(__file__).resolve().parent}{os.sep}filemonitor.py", '--url', f"{plugin.STASH_URL}"]
|
||||||
PythonExe = f"{sys.executable}"
|
plugin.ExecutePythonScript(args)
|
||||||
# PythonExe = PythonExe.replace("python.exe", "pythonw.exe")
|
|
||||||
args = [f"{PythonExe}", f"{pathlib.Path(__file__).resolve().parent}{os.sep}filemonitor.py", '--url', f"{plugin.STASH_URL}"]
|
|
||||||
plugin.Trace(f"args={args}")
|
|
||||||
if is_windows:
|
|
||||||
plugin.Trace("Executing process using Windows DETACHED_PROCESS")
|
|
||||||
DETACHED_PROCESS = 0x00000008
|
|
||||||
pid = subprocess.Popen(args,creationflags=DETACHED_PROCESS, shell=True).pid
|
|
||||||
else:
|
|
||||||
plugin.Trace("Executing process using normal Popen")
|
|
||||||
pid = subprocess.Popen(args).pid
|
|
||||||
plugin.Trace(f"pid={pid}")
|
|
||||||
|
|
||||||
if parse_args.stop or parse_args.restart or plugin.PLUGIN_TASK_NAME == "stop_library_monitor":
|
if parse_args.stop or parse_args.restart or plugin.PLUGIN_TASK_NAME == "stop_library_monitor":
|
||||||
stop_library_monitor()
|
stop_library_monitor()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: FileMonitor
|
name: FileMonitor
|
||||||
description: Monitors the Stash library folders, and updates Stash if any changes occurs in the Stash library paths.
|
description: Monitors the Stash library folders, and updates Stash if any changes occurs in the Stash library paths.
|
||||||
version: 0.7.7
|
version: 0.7.8
|
||||||
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/FileMonitor
|
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/FileMonitor
|
||||||
settings:
|
settings:
|
||||||
recursiveDisabled:
|
recursiveDisabled:
|
||||||
@@ -11,13 +11,13 @@ settings:
|
|||||||
displayName: Scheduler
|
displayName: Scheduler
|
||||||
description: Enable to turn on the scheduler. See filemonitor_config.py for more details.
|
description: Enable to turn on the scheduler. See filemonitor_config.py for more details.
|
||||||
type: BOOLEAN
|
type: BOOLEAN
|
||||||
|
zmaximumBackups:
|
||||||
|
displayName: Max DB Backups
|
||||||
|
description: When value greater than 1, will trim the number of database backup files to set value. Requires [Scheduler] enabled and backupDirectoryPath populated with path length longer than 4.
|
||||||
|
type: NUMBER
|
||||||
zzdebugTracing:
|
zzdebugTracing:
|
||||||
displayName: Debug Tracing
|
displayName: Debug Tracing
|
||||||
description: (Default=false) [***For Advanced Users***] Enable debug tracing. When enabled, additional tracing logging is added to Stash\plugins\FileMonitor\filemonitor.log
|
description: Enable debug tracing. When enabled, additional tracing logging is added to Stash\plugins\FileMonitor\filemonitor.log
|
||||||
type: BOOLEAN
|
|
||||||
zzdryRun:
|
|
||||||
displayName: Dry Run
|
|
||||||
description: Enable to run script in [Dry Run] mode. In this mode, Stash does NOT call meta_scan, and only logs the action it would have taken.
|
|
||||||
type: BOOLEAN
|
type: BOOLEAN
|
||||||
exec:
|
exec:
|
||||||
- python
|
- python
|
||||||
@@ -25,14 +25,14 @@ exec:
|
|||||||
interface: raw
|
interface: raw
|
||||||
tasks:
|
tasks:
|
||||||
- name: Start Library Monitor Service
|
- name: Start Library Monitor Service
|
||||||
description: Run as a SERVICE to monitors paths in Stash library for media file changes, and updates Stash. Recommended start method.
|
description: Run [Library Monitor] as a SERVICE to update Stash with any media file changes.
|
||||||
defaultArgs:
|
defaultArgs:
|
||||||
mode: start_library_monitor_service
|
mode: start_library_monitor_service
|
||||||
- name: Stop Library Monitor
|
- name: Stop Library Monitor
|
||||||
description: Stops library monitoring within 2 minute.
|
description: Stops library monitoring within 2 minute.
|
||||||
defaultArgs:
|
defaultArgs:
|
||||||
mode: stop_library_monitor
|
mode: stop_library_monitor
|
||||||
- name: Run as a Plugin
|
- name: Monitor as a Plugin
|
||||||
description: Run [Library Monitor] as a plugin (*not recommended method*)
|
description: Run [Library Monitor] as a plugin (*not recommended method*)
|
||||||
defaultArgs:
|
defaultArgs:
|
||||||
mode: start_library_monitor
|
mode: start_library_monitor
|
||||||
|
|||||||
@@ -42,16 +42,17 @@ config = {
|
|||||||
# Example monthly method.
|
# Example monthly method.
|
||||||
{"task" : "Backup", "weekday" : "sunday", "time" : "01:00", "monthly" : 2}, # Backup -> [Backup] 2nd sunday of the month at 1AM (01:00)
|
{"task" : "Backup", "weekday" : "sunday", "time" : "01:00", "monthly" : 2}, # Backup -> [Backup] 2nd sunday of the month at 1AM (01:00)
|
||||||
|
|
||||||
# The following is a place holder for a plugin.
|
# Example task for calling another Stash plugin, which needs plugin name and plugin ID.
|
||||||
{"task" : "PluginButtonName_Here", "pluginId" : "PluginId_Here", "hours" : 0}, # The zero frequency value makes this task disabled.
|
{"task" : "PluginButtonName_Here", "pluginId" : "PluginId_Here", "hours" : 0}, # The zero frequency value makes this task disabled.
|
||||||
# Add additional plugin task here.
|
# Add additional plugin task here.
|
||||||
|
|
||||||
|
# Example task to call a python script
|
||||||
|
{"task" : "python", "script" : "<plugin_path>test_script_hello_world.py", "args" : "--MyArguments Hello", "minutes" : 0},
|
||||||
|
|
||||||
|
# Example task to execute a command
|
||||||
|
{"task" : "execute", "command" : "C:\\MyPath\\HelloWorld.bat", "args" : "", "hours" : 0},
|
||||||
],
|
],
|
||||||
|
|
||||||
# Maximum backups to keep. When scheduler is enabled, and the Backup runs, delete older backups after reaching maximum backups.
|
|
||||||
"BackupsMax" : 12, # Only works if BackupDatabasePath is properly populated.
|
|
||||||
# The BACKUP database path. ToDo: Implement code to automate fetching this value
|
|
||||||
"BackupDatabasePath" : "C:\\Users\\admin3\\.stash\\DbBackup", # Example populated path
|
|
||||||
|
|
||||||
# When enabled, if CREATE flag is triggered, DupFileManager task is called if the plugin is installed.
|
# When enabled, if CREATE flag is triggered, DupFileManager task is called if the plugin is installed.
|
||||||
"onCreateCallDupFileManager": False, # Not yet implemented!!!!
|
"onCreateCallDupFileManager": False, # Not yet implemented!!!!
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user