forked from Github/Axter-Stash
Changed Plugin name
This commit is contained in:
525
plugins/FileMonitor/.gitignore
vendored
Normal file
525
plugins/FileMonitor/.gitignore
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
$ cat .gitignore
|
||||
|
||||
# Ignore these patterns
|
||||
desktop.ini
|
||||
~AutoRecover*.*
|
||||
*.aps
|
||||
*.exe
|
||||
*.idb
|
||||
*.ipch
|
||||
*.lib
|
||||
*.log
|
||||
*.log.1
|
||||
*.log.2
|
||||
*.manifest
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.sdf
|
||||
*.suo
|
||||
*.tlog
|
||||
*.user
|
||||
*.7z
|
||||
*.swp
|
||||
*.zip
|
||||
data.csv
|
||||
/boost
|
||||
/scintilla
|
||||
/bin
|
||||
/SQL
|
||||
/__pycache__
|
||||
__pycache__/
|
||||
renamefile_settings.cpython-310.pyc
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
##
|
||||
## Visual Studio Code
|
||||
##
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Other miscellaneous folders
|
||||
zzMiscellaneous/
|
||||
zzExcludeFromGithub/
|
||||
FromAnotherLanuageKit/
|
||||
_BadLanguages/
|
||||
|
||||
# Exclude test data and temp files
|
||||
Test_Data/
|
||||
*__ExcludeFromRepo__*.*
|
||||
*__DoNotAddToRepo__*.*
|
||||
deleteme/
|
||||
RelatedProjects/
|
||||
obj/
|
||||
|
||||
# Exclude temp and backup files
|
||||
*.bak
|
||||
|
||||
# ###########################################
|
||||
# Unique to this project
|
||||
# ###########################################
|
||||
# Exclude reparsepoint files which are used to help view file using VS
|
||||
*.xaml.xml
|
||||
gitignore.txt
|
||||
|
||||
GTranslate/obj/
|
||||
35
plugins/FileMonitor/README.md
Normal file
35
plugins/FileMonitor/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# FileMonitor: Ver 0.1.0 (By David Maisonave)
|
||||
FileMonitor is a [Stash](https://github.com/stashapp/stash) plugin which updates Stash if any changes occurs in the Stash library paths.
|
||||
|
||||
### Using FileMonitor as a plugin
|
||||
- To start monitoring file changes, go to **Stash->Settings->Task->[Plugin Tasks]->FileMonitor**, and click on the [Start Library Monitor] button.
|
||||
- 
|
||||
- To stop this task, go to **Stash->Settings->Task->[Task Queue]**, and click on the **[x]**.
|
||||
- 
|
||||
|
||||
### Using FileMonitor as a script
|
||||
**FileMonitor** can be called as a standalone script.
|
||||
- To start monitoring call the script and pass any argument.
|
||||
- python filemonitor.py **start**
|
||||
- To stop **FileMonitor**, pass argument **stop**.
|
||||
- python filemonitor.py **stop**
|
||||
- After running above command line, **FileMonitor** will stop after the next file change occurs.
|
||||
- The stop command works to stop the standalone job and the Stash plugin task job.
|
||||
|
||||
### Requirements
|
||||
`pip install stashapp-tools`
|
||||
`pip install pyYAML`
|
||||
`pip install watchdog`
|
||||
|
||||
### Installation
|
||||
- Follow **Requirements** instructions.
|
||||
- In the stash plugin directory (C:\Users\MyUserName\.stash\plugins), create a folder named **FileMonitor**.
|
||||
- Copy all the plugin files to this folder.(**C:\Users\MyUserName\\.stash\plugins\FileMonitor**).
|
||||
- Restart Stash.
|
||||
|
||||
That's it!!!
|
||||
|
||||
### Options
|
||||
- All options are accessible in the GUI via Settings->Plugins->Plugins->[FileMonitor].
|
||||
|
||||
|
||||
289
plugins/FileMonitor/filemonitor.py
Normal file
289
plugins/FileMonitor/filemonitor.py
Normal file
@@ -0,0 +1,289 @@
|
||||
# Description: This is a Stash plugin which updates Stash if any changes occurs in the Stash library paths.
|
||||
# By David Maisonave (aka Axter) Jul-2024 (https://www.axter.com/)
|
||||
# Get the latest developers version from following link: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/FileMonitor
|
||||
# Note: To call this script outside of Stash, pass any argument.
|
||||
# Example: python filemonitor.py foofoo
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import shutil
|
||||
import fileinput
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import requests
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import stashapi.log as log # Importing stashapi.log as log for critical events ONLY
|
||||
from stashapi.stashapp import StashInterface
|
||||
from watchdog.observers import Observer # This is also needed for event attributes
|
||||
import watchdog # pip install watchdog # https://pythonhosted.org/watchdog/
|
||||
from threading import Lock, Condition
|
||||
from multiprocessing import shared_memory
|
||||
from filemonitor_config import config # Import settings from filemonitor_config.py
|
||||
|
||||
# **********************************************************************
|
||||
# Constant global variables --------------------------------------------
|
||||
LOG_FILE_PATH = log_file_path = f"{Path(__file__).resolve().parent}\\{Path(__file__).stem}.log"
|
||||
FORMAT = "[%(asctime)s - LN:%(lineno)s] %(message)s"
|
||||
PLUGIN_ARGS = False
|
||||
PLUGIN_ARGS_MODE = False
|
||||
PLUGIN_ID = Path(__file__).stem.lower()
|
||||
# GraphQL query to fetch all scenes
|
||||
QUERY_ALL_SCENES = """
|
||||
query AllScenes {
|
||||
allScenes {
|
||||
id
|
||||
updated_at
|
||||
}
|
||||
}
|
||||
"""
|
||||
RFH = RotatingFileHandler(
|
||||
filename=LOG_FILE_PATH,
|
||||
mode='a',
|
||||
maxBytes=2*1024*1024, # Configure logging for this script with max log file size of 2000K
|
||||
backupCount=2,
|
||||
encoding=None,
|
||||
delay=0
|
||||
)
|
||||
TIMEOUT = 5
|
||||
CONTINUE_RUNNING_SIG = 99
|
||||
|
||||
# **********************************************************************
|
||||
# Global variables --------------------------------------------
|
||||
exitMsg = "Change success!!"
|
||||
mutex = Lock()
|
||||
signal = Condition(mutex)
|
||||
shouldUpdate = False
|
||||
TargetPaths = []
|
||||
runningInPluginMode = False
|
||||
|
||||
# Configure local log file for plugin within plugin folder having a limited max log file size
|
||||
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt="%y%m%d %H:%M:%S", handlers=[RFH])
|
||||
logger = logging.getLogger(Path(__file__).stem)
|
||||
|
||||
# **********************************************************************
|
||||
# ----------------------------------------------------------------------
|
||||
# Code section to fetch variables from Plugin UI and from filemonitor_settings.py
|
||||
# Check if being called as Stash plugin
|
||||
gettingCalledAsStashPlugin = True
|
||||
stopLibraryMonitoring = False
|
||||
StdInRead = None
|
||||
try:
|
||||
if len(sys.argv) == 1:
|
||||
print(f"Attempting to read stdin. (len(sys.argv)={len(sys.argv)})", file=sys.stderr)
|
||||
StdInRead = sys.stdin.read()
|
||||
# for line in fileinput.input():
|
||||
# StdInRead = line
|
||||
# break
|
||||
else:
|
||||
if len(sys.argv) > 1 and sys.argv[1].lower() == "stop":
|
||||
stopLibraryMonitoring = True
|
||||
raise Exception("Not called in plugin mode.")
|
||||
except:
|
||||
gettingCalledAsStashPlugin = False
|
||||
print(f"Either len(sys.argv) not expected value OR sys.stdin.read() failed! (stopLibraryMonitoring={stopLibraryMonitoring}) (StdInRead={StdInRead}) (len(sys.argv)={len(sys.argv)})", file=sys.stderr)
|
||||
pass
|
||||
|
||||
if gettingCalledAsStashPlugin and StdInRead:
|
||||
print(f"StdInRead={StdInRead} (len(sys.argv)={len(sys.argv)})", file=sys.stderr)
|
||||
runningInPluginMode = True
|
||||
json_input = json.loads(StdInRead)
|
||||
FRAGMENT_SERVER = json_input["server_connection"]
|
||||
else:
|
||||
runningInPluginMode = False
|
||||
FRAGMENT_SERVER = {'Scheme': config['endpoint_Scheme'], 'Host': config['endpoint_Host'], 'Port': config['endpoint_Port'], 'SessionCookie': {'Name': 'session', 'Value': '', 'Path': '', 'Domain': '', 'Expires': '0001-01-01T00:00:00Z', 'RawExpires': '', 'MaxAge': 0, 'Secure': False, 'HttpOnly': False, 'SameSite': 0, 'Raw': '', 'Unparsed': None}, 'Dir': os.path.dirname(Path(__file__).resolve().parent), 'PluginDir': Path(__file__).resolve().parent}
|
||||
print("Running in non-plugin mode!", file=sys.stderr)
|
||||
|
||||
stash = StashInterface(FRAGMENT_SERVER)
|
||||
PLUGINCONFIGURATION = stash.get_configuration()["plugins"]
|
||||
STASHCONFIGURATION = stash.get_configuration()["general"]
|
||||
STASHPATHSCONFIG = STASHCONFIGURATION['stashes']
|
||||
stashPaths = []
|
||||
settings = {
|
||||
"recursiveDisabled": False,
|
||||
"runCleanAfterDelete": False,
|
||||
"scanModified": False,
|
||||
"zzdebugTracing": False,
|
||||
"zzdryRun": False,
|
||||
}
|
||||
|
||||
if PLUGIN_ID in PLUGINCONFIGURATION:
|
||||
settings.update(PLUGINCONFIGURATION[PLUGIN_ID])
|
||||
# ----------------------------------------------------------------------
|
||||
debugTracing = settings["zzdebugTracing"]
|
||||
RECURSIVE = settings["recursiveDisabled"] == False
|
||||
SCAN_MODIFIED = settings["scanModified"]
|
||||
RUN_CLEAN_AFTER_DELETE = settings["runCleanAfterDelete"]
|
||||
RUN_GENERATE_CONTENT = config['runGenerateContent']
|
||||
|
||||
for item in STASHPATHSCONFIG:
|
||||
stashPaths.append(item["path"])
|
||||
|
||||
# Extract dry_run setting from settings
|
||||
DRY_RUN = settings["zzdryRun"]
|
||||
dry_run_prefix = ''
|
||||
try:
|
||||
PLUGIN_ARGS = json_input['args']
|
||||
PLUGIN_ARGS_MODE = json_input['args']["mode"]
|
||||
except:
|
||||
pass
|
||||
logger.info(f"\nStarting (runningInPluginMode={runningInPluginMode}) (debugTracing={debugTracing}) (DRY_RUN={DRY_RUN}) (PLUGIN_ARGS_MODE={PLUGIN_ARGS_MODE}) (PLUGIN_ARGS={PLUGIN_ARGS})************************************************")
|
||||
if debugTracing: logger.info(f"Debug Tracing (stash.get_configuration()={stash.get_configuration()})................")
|
||||
if debugTracing: logger.info("settings: %s " % (settings,))
|
||||
if debugTracing: logger.info(f"Debug Tracing (STASHCONFIGURATION={STASHCONFIGURATION})................")
|
||||
if debugTracing: logger.info(f"Debug Tracing (stashPaths={stashPaths})................")
|
||||
|
||||
if DRY_RUN:
|
||||
logger.info("Dry run mode is enabled.")
|
||||
dry_run_prefix = "Would've "
|
||||
if debugTracing: logger.info("Debug Tracing................")
|
||||
# ----------------------------------------------------------------------
|
||||
# **********************************************************************
|
||||
if debugTracing: logger.info(f"Debug Tracing (SCAN_MODIFIED={SCAN_MODIFIED}) (RECURSIVE={RECURSIVE})................")
|
||||
|
||||
def start_library_monitor():
|
||||
global shouldUpdate
|
||||
global TargetPaths
|
||||
try:
|
||||
# Create shared memory buffer which can be used as singleton logic or to get a signal to quit task from external script
|
||||
shm_a = shared_memory.SharedMemory(name="DavidMaisonaveAxter_FileMonitor", create=True, size=4)
|
||||
except:
|
||||
pass
|
||||
logger.info("Could not open shared memory map. Change File Monitor must be running. Can not run multiple instance of Change File Monitor.")
|
||||
return
|
||||
type(shm_a.buf)
|
||||
shm_buffer = shm_a.buf
|
||||
len(shm_buffer)
|
||||
shm_buffer[0] = CONTINUE_RUNNING_SIG
|
||||
if debugTracing: logger.info(f"Shared memory map opended, and flag set to {shm_buffer[0]}")
|
||||
RunCleanMetadata = False
|
||||
|
||||
event_handler = watchdog.events.FileSystemEventHandler()
|
||||
def on_created(event):
|
||||
global shouldUpdate
|
||||
global TargetPaths
|
||||
TargetPaths.append(event.src_path)
|
||||
logger.info(f"CREATE *** '{event.src_path}'")
|
||||
with mutex:
|
||||
shouldUpdate = True
|
||||
signal.notify()
|
||||
|
||||
def on_deleted(event):
|
||||
global shouldUpdate
|
||||
global TargetPaths
|
||||
nonlocal RunCleanMetadata
|
||||
TargetPaths.append(event.src_path)
|
||||
logger.info(f"DELETE *** '{event.src_path}'")
|
||||
with mutex:
|
||||
shouldUpdate = True
|
||||
RunCleanMetadata = True
|
||||
signal.notify()
|
||||
|
||||
def on_modified(event):
|
||||
global shouldUpdate
|
||||
global TargetPaths
|
||||
if SCAN_MODIFIED:
|
||||
TargetPaths.append(event.src_path)
|
||||
logger.info(f"MODIFIED *** '{event.src_path}'")
|
||||
with mutex:
|
||||
shouldUpdate = True
|
||||
signal.notify()
|
||||
else:
|
||||
if debugTracing: logger.info(f"Ignoring modifications due to plugin UI setting. path='{event.src_path}'")
|
||||
|
||||
def on_moved(event):
|
||||
global shouldUpdate
|
||||
global TargetPaths
|
||||
TargetPaths.append(event.src_path)
|
||||
TargetPaths.append(event.dest_path)
|
||||
logger.info(f"MOVE *** from '{event.src_path}' to '{event.dest_path}'")
|
||||
with mutex:
|
||||
shouldUpdate = True
|
||||
signal.notify()
|
||||
|
||||
if debugTracing: logger.info("Debug Trace........")
|
||||
event_handler.on_created = on_created
|
||||
event_handler.on_deleted = on_deleted
|
||||
event_handler.on_modified = on_modified
|
||||
event_handler.on_moved = on_moved
|
||||
|
||||
observer = Observer()
|
||||
|
||||
# Iterate through stashPaths
|
||||
for path in stashPaths:
|
||||
observer.schedule(event_handler, path, recursive=RECURSIVE)
|
||||
if debugTracing: logger.info(f"Observing {path}")
|
||||
observer.start()
|
||||
if debugTracing: logger.info("Starting loop................")
|
||||
try:
|
||||
while True:
|
||||
TmpTargetPaths = []
|
||||
with mutex:
|
||||
while not shouldUpdate:
|
||||
if debugTracing: logger.info("Wait start................")
|
||||
signal.wait()
|
||||
if debugTracing: logger.info("Wait end................")
|
||||
shouldUpdate = False
|
||||
TmpTargetPaths = []
|
||||
for TargetPath in TargetPaths:
|
||||
TmpTargetPaths.append(os.path.dirname(TargetPath))
|
||||
TargetPaths = []
|
||||
TmpTargetPaths = list(set(TmpTargetPaths))
|
||||
if TmpTargetPaths != []:
|
||||
logger.info(f"Triggering stash scan for path(s) {TmpTargetPaths}")
|
||||
if not DRY_RUN:
|
||||
stash.metadata_scan(paths=TmpTargetPaths)
|
||||
if RUN_CLEAN_AFTER_DELETE and RunCleanMetadata:
|
||||
stash.metadata_clean(paths=TmpTargetPaths, dry_run=DRY_RUN)
|
||||
if RUN_GENERATE_CONTENT:
|
||||
stash.metadata_generate()
|
||||
if gettingCalledAsStashPlugin and shm_buffer[0] == CONTINUE_RUNNING_SIG:
|
||||
stash.run_plugin_task(plugin_id=PLUGIN_ID, task_name="Start Library Monitor")
|
||||
if debugTracing: logger.info("Exiting plugin so that metadata_scan task can run.")
|
||||
return
|
||||
else:
|
||||
if debugTracing: logger.info("Nothing to scan.")
|
||||
if shm_buffer[0] != CONTINUE_RUNNING_SIG:
|
||||
logger.info(f"Exiting Change File Monitor. (shm_buffer[0]={shm_buffer[0]})")
|
||||
shm_a.close()
|
||||
shm_a.unlink() # Call unlink only once to release the shared memory
|
||||
raise KeyboardInterrupt
|
||||
except KeyboardInterrupt:
|
||||
observer.stop()
|
||||
if debugTracing: logger.info("Stopping observer................")
|
||||
observer.join()
|
||||
if debugTracing: logger.info("Exiting function................")
|
||||
|
||||
# This function is only useful when called outside of Stash.
|
||||
# Example: python filemonitor.py stop
|
||||
# Stops monitoring after triggered by the next file change.
|
||||
# ToDo: Add logic so it doesn't have to wait until the next file change
|
||||
def stop_library_monitor():
|
||||
if debugTracing: logger.info("Opening shared memory map.")
|
||||
try:
|
||||
shm_a = shared_memory.SharedMemory(name="DavidMaisonaveAxter_FileMonitor", create=False, size=4)
|
||||
except:
|
||||
pass
|
||||
logger.info("Could not open shared memory map. Change File Monitor must not be running.")
|
||||
return
|
||||
type(shm_a.buf)
|
||||
shm_buffer = shm_a.buf
|
||||
len(shm_buffer)
|
||||
shm_buffer[0] = 123
|
||||
if debugTracing: logger.info(f"Shared memory map opended, and flag set to {shm_buffer[0]}")
|
||||
shm_a.close()
|
||||
shm_a.unlink() # Call unlink only once to release the shared memory
|
||||
time.sleep(1)
|
||||
return
|
||||
|
||||
if stopLibraryMonitoring:
|
||||
stop_library_monitor()
|
||||
if debugTracing: logger.info(f"stop_library_monitor EXIT................")
|
||||
elif PLUGIN_ARGS_MODE == "start_library_monitor" or not gettingCalledAsStashPlugin:
|
||||
start_library_monitor()
|
||||
if debugTracing: logger.info(f"start_library_monitor EXIT................")
|
||||
else:
|
||||
logger.info(f"Nothing to do!!! (PLUGIN_ARGS_MODE={PLUGIN_ARGS_MODE})")
|
||||
|
||||
if debugTracing: logger.info("\n*********************************\nEXITING ***********************\n*********************************")
|
||||
34
plugins/FileMonitor/filemonitor.yml
Normal file
34
plugins/FileMonitor/filemonitor.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
name: FileMonitor
|
||||
description: Monitors the Stash library folders, and updates Stash if any changes occurs in the Stash library paths.
|
||||
version: 0.2.0
|
||||
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/FileMonitor
|
||||
settings:
|
||||
recursiveDisabled:
|
||||
displayName: No Recursive
|
||||
description: Enable stop monitoring paths recursively.
|
||||
type: BOOLEAN
|
||||
runCleanAfterDelete:
|
||||
displayName: Run Clean
|
||||
description: Enable to run metadata clean task after file deletion.
|
||||
type: BOOLEAN
|
||||
scanModified:
|
||||
displayName: Scan Modifications
|
||||
description: Enable to monitor changes in file system for modification flag. This option is NOT needed for Windows, because on Windows changes are triggered via CREATE, DELETE, and MOVE flags. Other OS may differ.
|
||||
type: BOOLEAN
|
||||
zzdebugTracing:
|
||||
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
|
||||
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
|
||||
exec:
|
||||
- python
|
||||
- "{pluginDir}/filemonitor.py"
|
||||
interface: raw
|
||||
tasks:
|
||||
- name: Start Library Monitor
|
||||
description: Monitors paths in Stash library for media file changes, and updates Stash.
|
||||
defaultArgs:
|
||||
mode: start_library_monitor
|
||||
12
plugins/FileMonitor/filemonitor_config.py
Normal file
12
plugins/FileMonitor/filemonitor_config.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Description: This is a Stash plugin which updates Stash if any changes occurs in the Stash library paths.
|
||||
# By David Maisonave (aka Axter) Jul-2024 (https://www.axter.com/)
|
||||
# Get the latest developers version from following link: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/FileMonitor
|
||||
config = {
|
||||
# Enable to run metadata_generate (Generate Content) after metadata scan.
|
||||
"runGenerateContent": False,
|
||||
|
||||
# The following fields are ONLY used when running FileMonitor in script mode
|
||||
"endpoint_Scheme" : "http", # Define endpoint to use when contacting the Stash server
|
||||
"endpoint_Host" : "0.0.0.0", # Define endpoint to use when contacting the Stash server
|
||||
"endpoint_Port" : 9999, # Define endpoint to use when contacting the Stash server
|
||||
}
|
||||
4
plugins/FileMonitor/requirements.txt
Normal file
4
plugins/FileMonitor/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
stashapp-tools
|
||||
pyYAML
|
||||
watchdog
|
||||
requests
|
||||
Reference in New Issue
Block a user