forked from Github/Axter-Stash
Added logic to allow RenameFile to close open file handles before renaming
This commit is contained in:
@@ -1,29 +1,31 @@
|
||||
"""
|
||||
StashPluginHelper (By David Maisonave aka Axter)
|
||||
See end of this file for example usage
|
||||
Log Features:
|
||||
Can optionally log out to multiple outputs for each Log or Trace call.
|
||||
Logging includes source code line number
|
||||
Sets a maximum plugin log file size
|
||||
Stash Interface Features:
|
||||
Gets STASH_URL value from command line argument and/or from STDIN_READ
|
||||
Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
||||
Sets PLUGIN_ID based on the main script file name (in lower case)
|
||||
Gets PLUGIN_TASK_NAME value
|
||||
Sets pluginSettings (The plugin UI settings)
|
||||
Misc Features:
|
||||
Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
||||
Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
||||
Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
||||
Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
||||
"""
|
||||
from stashapi.stashapp import StashInterface
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import re, inspect, sys, os, pathlib, logging, json
|
||||
import re, inspect, sys, os, pathlib, logging, json, ctypes
|
||||
import concurrent.futures
|
||||
from stashapi.stash_types import PhashDistance
|
||||
import __main__
|
||||
|
||||
_ARGUMENT_UNSPECIFIED_ = "_ARGUMENT_UNSPECIFIED_"
|
||||
|
||||
# StashPluginHelper (By David Maisonave aka Axter)
|
||||
# See end of this file for example usage
|
||||
# Log Features:
|
||||
# Can optionally log out to multiple outputs for each Log or Trace call.
|
||||
# Logging includes source code line number
|
||||
# Sets a maximum plugin log file size
|
||||
# Stash Interface Features:
|
||||
# Gets STASH_URL value from command line argument and/or from STDIN_READ
|
||||
# Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
||||
# Sets PLUGIN_ID based on the main script file name (in lower case)
|
||||
# Gets PLUGIN_TASK_NAME value
|
||||
# Sets pluginSettings (The plugin UI settings)
|
||||
# Misc Features:
|
||||
# Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
||||
# Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
||||
# Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
||||
# Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
||||
class StashPluginHelper(StashInterface):
|
||||
# Primary Members for external reference
|
||||
PLUGIN_TASK_NAME = None
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
"""
|
||||
StashPluginHelper (By David Maisonave aka Axter)
|
||||
See end of this file for example usage
|
||||
Log Features:
|
||||
Can optionally log out to multiple outputs for each Log or Trace call.
|
||||
Logging includes source code line number
|
||||
Sets a maximum plugin log file size
|
||||
Stash Interface Features:
|
||||
Gets STASH_URL value from command line argument and/or from STDIN_READ
|
||||
Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
||||
Sets PLUGIN_ID based on the main script file name (in lower case)
|
||||
Gets PLUGIN_TASK_NAME value
|
||||
Sets pluginSettings (The plugin UI settings)
|
||||
Misc Features:
|
||||
Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
||||
Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
||||
Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
||||
Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
||||
"""
|
||||
from stashapi.stashapp import StashInterface
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import re, inspect, sys, os, pathlib, logging, json
|
||||
import re, inspect, sys, os, pathlib, logging, json, ctypes
|
||||
import concurrent.futures
|
||||
from stashapi.stash_types import PhashDistance
|
||||
import __main__
|
||||
|
||||
_ARGUMENT_UNSPECIFIED_ = "_ARGUMENT_UNSPECIFIED_"
|
||||
|
||||
# StashPluginHelper (By David Maisonave aka Axter)
|
||||
# See end of this file for example usage
|
||||
# Log Features:
|
||||
# Can optionally log out to multiple outputs for each Log or Trace call.
|
||||
# Logging includes source code line number
|
||||
# Sets a maximum plugin log file size
|
||||
# Stash Interface Features:
|
||||
# Gets STASH_URL value from command line argument and/or from STDIN_READ
|
||||
# Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
||||
# Sets PLUGIN_ID based on the main script file name (in lower case)
|
||||
# Gets PLUGIN_TASK_NAME value
|
||||
# Sets pluginSettings (The plugin UI settings)
|
||||
# Misc Features:
|
||||
# Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
||||
# Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
||||
# Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
||||
# Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
||||
class StashPluginHelper(StashInterface):
|
||||
# Primary Members for external reference
|
||||
PLUGIN_TASK_NAME = None
|
||||
|
||||
@@ -592,9 +592,9 @@ def start_library_monitor():
|
||||
if lastScanJob['timeOutDelayProcess'] > MAX_TIMEOUT_FOR_DELAY_PATH_PROCESS:
|
||||
lastScanJob['timeOutDelayProcess'] = MAX_TIMEOUT_FOR_DELAY_PATH_PROCESS
|
||||
timeOutInSeconds = lastScanJob['timeOutDelayProcess']
|
||||
stash.LogOnce(f"Awaiting file change-trigger, with a short timeout ({timeOutInSeconds} seconds), because of active delay path processing.")
|
||||
stash.Log(f"Awaiting file change-trigger, with a short timeout ({timeOutInSeconds} seconds), because of active delay path processing.")
|
||||
else:
|
||||
stash.LogOnce(f"Waiting for a file change-trigger. Timeout = {timeOutInSeconds} seconds.")
|
||||
stash.Log(f"Waiting for a file change-trigger. Timeout = {timeOutInSeconds} seconds.")
|
||||
signal.wait(timeout=timeOutInSeconds)
|
||||
if lastScanJob['DelayedProcessTargetPaths'] != []:
|
||||
stash.TraceOnce(f"Processing delay scan for path(s) {lastScanJob['DelayedProcessTargetPaths']}")
|
||||
@@ -727,7 +727,11 @@ def start_library_monitor_service():
|
||||
args = args + ["-a", stash.API_KEY]
|
||||
stash.ExecutePythonScript(args)
|
||||
|
||||
runTypeID=0
|
||||
runTypeName=["NothingToDo", "stop_library_monitor", "StartFileMonitorAsAServiceTaskID", "StartFileMonitorAsAPluginTaskID", "CommandLineStartLibMonitor"]
|
||||
try:
|
||||
if parse_args.stop or parse_args.restart or stash.PLUGIN_TASK_NAME == "stop_library_monitor":
|
||||
runTypeID=1
|
||||
stop_library_monitor()
|
||||
if parse_args.restart:
|
||||
time.sleep(5)
|
||||
@@ -736,20 +740,22 @@ if parse_args.stop or parse_args.restart or stash.PLUGIN_TASK_NAME == "stop_libr
|
||||
else:
|
||||
stash.Trace(f"Stop FileMonitor EXIT")
|
||||
elif stash.PLUGIN_TASK_NAME == StartFileMonitorAsAServiceTaskID:
|
||||
runTypeID=2
|
||||
start_library_monitor_service()
|
||||
stash.Trace(f"{StartFileMonitorAsAServiceTaskID} EXIT")
|
||||
elif stash.PLUGIN_TASK_NAME == StartFileMonitorAsAPluginTaskID:
|
||||
runTypeID=3
|
||||
start_library_monitor()
|
||||
stash.Trace(f"{StartFileMonitorAsAPluginTaskID} EXIT")
|
||||
elif not stash.CALLED_AS_STASH_PLUGIN:
|
||||
try:
|
||||
runTypeID=4
|
||||
start_library_monitor()
|
||||
stash.Trace("Command line FileMonitor EXIT")
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
stash.Error(f"Exception while running FileMonitor from the command line. Error: {e}\nTraceBack={tb}")
|
||||
stash.log.exception('Got exception on main handler')
|
||||
else:
|
||||
stash.Log(f"Nothing to do!!! (stash.PLUGIN_TASK_NAME={stash.PLUGIN_TASK_NAME})")
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
stash.Error(f"Exception while running FileMonitor. runType='{runTypeName[runTypeID]}'; Error: {e}\nTraceBack={tb}")
|
||||
stash.log.exception('Got exception on main handler')
|
||||
|
||||
stash.Trace("\n*********************************\nEXITING ***********************\n*********************************")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RenameFile: Ver 0.4.6 (By David Maisonave)
|
||||
# RenameFile: Ver 0.5.0 (By David Maisonave)
|
||||
RenameFile is a [Stash](https://github.com/stashapp/stash) plugin which performs the following tasks.
|
||||
- **Rename Scene File Name** (On-The-Fly)
|
||||
- **Append tag names** to file name
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
"""
|
||||
StashPluginHelper (By David Maisonave aka Axter)
|
||||
See end of this file for example usage
|
||||
Log Features:
|
||||
Can optionally log out to multiple outputs for each Log or Trace call.
|
||||
Logging includes source code line number
|
||||
Sets a maximum plugin log file size
|
||||
Stash Interface Features:
|
||||
Gets STASH_URL value from command line argument and/or from STDIN_READ
|
||||
Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
||||
Sets PLUGIN_ID based on the main script file name (in lower case)
|
||||
Gets PLUGIN_TASK_NAME value
|
||||
Sets pluginSettings (The plugin UI settings)
|
||||
Misc Features:
|
||||
Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
||||
Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
||||
Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
||||
Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
||||
"""
|
||||
from stashapi.stashapp import StashInterface
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import re, inspect, sys, os, pathlib, logging, json
|
||||
import re, inspect, sys, os, pathlib, logging, json, ctypes
|
||||
import concurrent.futures
|
||||
from stashapi.stash_types import PhashDistance
|
||||
import __main__
|
||||
|
||||
_ARGUMENT_UNSPECIFIED_ = "_ARGUMENT_UNSPECIFIED_"
|
||||
|
||||
# StashPluginHelper (By David Maisonave aka Axter)
|
||||
# See end of this file for example usage
|
||||
# Log Features:
|
||||
# Can optionally log out to multiple outputs for each Log or Trace call.
|
||||
# Logging includes source code line number
|
||||
# Sets a maximum plugin log file size
|
||||
# Stash Interface Features:
|
||||
# Gets STASH_URL value from command line argument and/or from STDIN_READ
|
||||
# Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
||||
# Sets PLUGIN_ID based on the main script file name (in lower case)
|
||||
# Gets PLUGIN_TASK_NAME value
|
||||
# Sets pluginSettings (The plugin UI settings)
|
||||
# Misc Features:
|
||||
# Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
||||
# Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
||||
# Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
||||
# Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
||||
class StashPluginHelper(StashInterface):
|
||||
# Primary Members for external reference
|
||||
PLUGIN_TASK_NAME = None
|
||||
|
||||
187
plugins/RenameFile/openedFile.py
Normal file
187
plugins/RenameFile/openedFile.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
openedFile (By David Maisonave aka Axter)
|
||||
https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/RenameFile
|
||||
|
||||
Description:
|
||||
Close all (open file) handles on all processes for a given file.
|
||||
Use Case:
|
||||
Can be used when a file needs to be deleted or moved,
|
||||
but the file is locked by one or more other processes.
|
||||
Requirements:
|
||||
This class requires Sysinternals handle.exe, which can be downloaded from following link:
|
||||
https://learn.microsoft.com/en-us/sysinternals/downloads/handle
|
||||
Important: **MUST call this class in admin mode with elevated privileges!!!**
|
||||
Example Usage:
|
||||
handleExe = r"C:\Sysinternals\handle64.exe"
|
||||
of = openedFile(handleExe)
|
||||
of.closeFile(r"B:\V\V\testdup\deleme2.mp4")
|
||||
|
||||
"""
|
||||
import ctypes, os, sys, psutil, argparse, traceback, logging, numbers, string
|
||||
from ctypes import wintypes
|
||||
# from StashPluginHelper import StashPluginHelper
|
||||
# Look at the following links to enhance this code:
|
||||
# https://stackoverflow.com/questions/35106511/how-to-access-the-peb-of-another-process-with-python-ctypes
|
||||
# https://www.codeproject.com/Articles/19685/Get-Process-Info-with-NtQueryInformationProcess
|
||||
|
||||
# Important: MUST call this class in admin mode with elevated privileges!!!
|
||||
# This class has member function runMeAsAdmin, which will elevate privileges.
|
||||
# When member function closeFile is called, it will call runMeAsAdmin as needed.
|
||||
# getPid is the only function which does NOT require elevated admin privileges.
|
||||
class openedFile():
|
||||
# generic strings and constants
|
||||
ntdll = ctypes.WinDLL('ntdll')
|
||||
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
||||
NTSTATUS = wintypes.LONG
|
||||
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
|
||||
FILE_READ_ATTRIBUTES = 0x80
|
||||
FILE_SHARE_READ = 1
|
||||
OPEN_EXISTING = 3
|
||||
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
|
||||
FILE_INFORMATION_CLASS = wintypes.ULONG
|
||||
FileProcessIdsUsingFileInformation = 47 # see https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_file_information_class
|
||||
LPSECURITY_ATTRIBUTES = wintypes.LPVOID
|
||||
ULONG_PTR = wintypes.WPARAM
|
||||
lastPath = None
|
||||
handleExe = None
|
||||
stash = None
|
||||
|
||||
def __init__(self, handleExe, stash = None):
|
||||
self.handleExe = handleExe
|
||||
self.stash = stash
|
||||
if handleExe == None or handleExe == "" or not os.path.isfile(handleExe):
|
||||
raise Exception(f"handleExe requires a valid path to Sysinternals 'handle.exe' or 'handle64.exe' executable. Can be downloaded from following link:\nhttps://learn.microsoft.com/en-us/sysinternals/downloads/handle")
|
||||
# create handle on concerned file with dwDesiredAccess == self.FILE_READ_ATTRIBUTES
|
||||
self.kernel32.CreateFileW.restype = wintypes.HANDLE
|
||||
self.kernel32.CreateFileW.argtypes = (
|
||||
wintypes.LPCWSTR, # In lpFileName
|
||||
wintypes.DWORD, # In dwDesiredAccess
|
||||
wintypes.DWORD, # In dwShareMode
|
||||
self.LPSECURITY_ATTRIBUTES, # In_opt lpSecurityAttributes
|
||||
wintypes.DWORD, # In dwCreationDisposition
|
||||
wintypes.DWORD, # In dwFlagsAndAttributes
|
||||
wintypes.HANDLE) # In_opt hTemplateFile
|
||||
|
||||
def getPid(self, path):
|
||||
self.lastPath = path
|
||||
hFile = self.kernel32.CreateFileW(
|
||||
path, self.FILE_READ_ATTRIBUTES, self.FILE_SHARE_READ, None, self.OPEN_EXISTING,
|
||||
self.FILE_FLAG_BACKUP_SEMANTICS, None)
|
||||
if hFile == self.INVALID_HANDLE_VALUE:
|
||||
raise ctypes.WinError(ctypes.get_last_error())
|
||||
# prepare data types for system call
|
||||
class IO_STATUS_BLOCK(ctypes.Structure):
|
||||
class _STATUS(ctypes.Union):
|
||||
_fields_ = (('Status', self.NTSTATUS),
|
||||
('Pointer', wintypes.LPVOID))
|
||||
_anonymous_ = '_Status',
|
||||
_fields_ = (('_Status', _STATUS),
|
||||
('Information', self.ULONG_PTR))
|
||||
iosb = IO_STATUS_BLOCK()
|
||||
class FILE_PROCESS_IDS_USING_FILE_INFORMATION(ctypes.Structure):
|
||||
_fields_ = (('NumberOfProcessIdsInList', wintypes.LARGE_INTEGER),
|
||||
('ProcessIdList', wintypes.LARGE_INTEGER * 64))
|
||||
info = FILE_PROCESS_IDS_USING_FILE_INFORMATION()
|
||||
PIO_STATUS_BLOCK = ctypes.POINTER(IO_STATUS_BLOCK)
|
||||
self.ntdll.NtQueryInformationFile.restype = self.NTSTATUS
|
||||
self.ntdll.NtQueryInformationFile.argtypes = (
|
||||
wintypes.HANDLE, # In FileHandle
|
||||
PIO_STATUS_BLOCK, # Out IoStatusBlock
|
||||
wintypes.LPVOID, # Out FileInformation
|
||||
wintypes.ULONG, # In Length
|
||||
self.FILE_INFORMATION_CLASS) # In FileInformationClass
|
||||
# system call to retrieve list of PIDs currently using the file
|
||||
status = self.ntdll.NtQueryInformationFile(hFile, ctypes.byref(iosb),
|
||||
ctypes.byref(info),
|
||||
ctypes.sizeof(info),
|
||||
self.FileProcessIdsUsingFileInformation)
|
||||
pidList = info.ProcessIdList[0:info.NumberOfProcessIdsInList]
|
||||
if len(pidList) > 0:
|
||||
return pidList
|
||||
return None
|
||||
|
||||
def isAdmin(self):
|
||||
if os.name=='nt':
|
||||
try:
|
||||
return ctypes.windll.shell32.IsUserAnAdmin()
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return os.getuid() == 0 # For unix like systems
|
||||
|
||||
def runMeAsAdmin(self):
|
||||
if self.isAdmin() == True:
|
||||
return
|
||||
if os.name=='nt':
|
||||
# Below is a Windows only method which does NOT popup a console.
|
||||
import win32com.shell.shell as shell # Requires: pip install pywin32
|
||||
script = os.path.abspath(sys.argv[0])
|
||||
params = ' '.join([script] + sys.argv[1:])
|
||||
shell.ShellExecuteEx(lpVerb='runas', lpFile=sys.executable, lpParameters=params)
|
||||
sys.exit(0)
|
||||
else:
|
||||
from elevate import elevate # Requires: pip install elevate
|
||||
elevate()
|
||||
|
||||
def getPidExeFileName(self, pid): # Requires running with admin privileges.
|
||||
import win32api, win32con, win32process
|
||||
handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, pid) #get handle for the pid
|
||||
filename = win32process.GetModuleFileNameEx(handle, 0) #get exe path & filename for handle
|
||||
return filename
|
||||
|
||||
def getFilesOpen(self, pid:int): # Requires running with admin privileges.
|
||||
p = psutil.Process(pid1)
|
||||
return p.open_files()
|
||||
|
||||
def getFileHandle(self, pid, path = None): # Requires running with admin privileges.
|
||||
if path == None:
|
||||
path = self.lastPath
|
||||
args = f"{self.handleExe} -p {pid} -nobanner"
|
||||
# if self.stash != None: self.stash.Log(args)
|
||||
results = os.popen(args).read()
|
||||
results = results.splitlines()
|
||||
# if self.stash != None: self.stash.Log(results)
|
||||
hdls = []
|
||||
for line in results:
|
||||
# if self.stash != None: self.stash.Log(line)
|
||||
if line.endswith(path):
|
||||
epos = line.find(":")
|
||||
if epos > 0:
|
||||
hdls += [line[0:epos]]
|
||||
else:
|
||||
break
|
||||
if len(hdls) == 0:
|
||||
return None
|
||||
return hdls
|
||||
|
||||
def closeHandle(self, pid, fileHandle): # Requires running with admin privileges.
|
||||
args = f"{self.handleExe} -p {pid} -c {fileHandle} -y -nobanner"
|
||||
if self.stash != None: self.stash.Log(args)
|
||||
results = os.popen(args).read()
|
||||
results = results.strip("\n")
|
||||
if results.endswith("Handle closed."):
|
||||
return True
|
||||
if self.stash != None: self.stash.Error(f"Could not close pid {pid} file handle {fileHandle}; results={results}")
|
||||
return False
|
||||
|
||||
def closeFile(self, path): # Requires running with admin privileges.
|
||||
pids = self.getPid(path)
|
||||
if pids == None:
|
||||
return None
|
||||
# if self.stash != None: self.stash.Log(f"pids={pids}")
|
||||
results = []
|
||||
|
||||
# Need admin privileges starting here.
|
||||
self.runMeAsAdmin()
|
||||
for pid in pids:
|
||||
hdls = self.getFileHandle(pid, path)
|
||||
if hdls == None:
|
||||
# if self.stash != None: self.stash.Log(f"No handle for pid {pid}")
|
||||
continue
|
||||
else:
|
||||
for hdl in hdls:
|
||||
# if self.stash != None: self.stash.Log(f"pid {pid} hdl={hdl}")
|
||||
results += [self.closeHandle(pid, hdl)]
|
||||
if len(results) == 0:
|
||||
return None
|
||||
return {"results" : results, "pids" : pids}
|
||||
@@ -8,6 +8,7 @@ import stashapi.log as log # Importing stashapi.log as log for critical events O
|
||||
from stashapi.stashapp import StashInterface
|
||||
from StashPluginHelper import StashPluginHelper
|
||||
from renamefile_settings import config # Import settings from renamefile_settings.py
|
||||
from openedFile import openedFile
|
||||
|
||||
# **********************************************************************
|
||||
# Constant global variables --------------------------------------------
|
||||
@@ -76,6 +77,10 @@ tag_whitelist = config["tagWhitelist"]
|
||||
if not tag_whitelist:
|
||||
tag_whitelist = ""
|
||||
stash.Trace(f"(tag_whitelist={tag_whitelist})")
|
||||
handleExe = stash.pluginConfig['handleExe']
|
||||
openedfile = None
|
||||
if handleExe != None and handleExe != "" and os.path.isfile(handleExe):
|
||||
openedfile = openedFile(handleExe, stash)
|
||||
|
||||
endpointHost = stash.JSON_INPUT['server_connection']['Host']
|
||||
if endpointHost == "0.0.0.0":
|
||||
@@ -299,6 +304,10 @@ def rename_scene(scene_id):
|
||||
return None
|
||||
targetDidExist = True if os.path.isfile(new_file_path) else False
|
||||
try:
|
||||
if openedfile != None:
|
||||
results = openedfile.closeFile(original_file_path)
|
||||
if results != None:
|
||||
stash.Warn(f"Had to close '{original_file_path}', because it was opened by following pids:{results['pids']}")
|
||||
if move_files:
|
||||
if not dry_run:
|
||||
shutil.move(original_file_path, new_file_path)
|
||||
@@ -340,12 +349,15 @@ def rename_files_task():
|
||||
stash.Log("No changes were made.")
|
||||
return
|
||||
|
||||
try:
|
||||
if stash.PLUGIN_TASK_NAME == "rename_files_task":
|
||||
rename_files_task()
|
||||
elif inputToUpdateScenePost:
|
||||
rename_files_task()
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
stash.Error(f"Exception while running Plugin. Error: {e}\nTraceBack={tb}")
|
||||
stash.log.exception('Got exception on main handler')
|
||||
|
||||
stash.Trace("\n*********************************\nEXITING ***********************\n*********************************")
|
||||
|
||||
# ToDo: Wish List
|
||||
# Add code to get tags from duplicate filenames
|
||||
@@ -1,6 +1,6 @@
|
||||
name: RenameFile
|
||||
description: Renames video (scene) file names when the user edits the [Title] field located in the scene [Edit] tab.
|
||||
version: 0.4.6
|
||||
version: 0.5.0
|
||||
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/RenameFile
|
||||
settings:
|
||||
performerAppend:
|
||||
|
||||
@@ -47,4 +47,16 @@ config = {
|
||||
"if_notitle_use_org_filename": True, # Warning: Do not recommend setting this to False.
|
||||
# Current Stash DB schema only allows maximum base file name length to be 255
|
||||
"max_filename_length": 255,
|
||||
|
||||
# handleExe is for Windows only.
|
||||
# In Windows, a file can't be renamed if the file is opened by another process.
|
||||
# In other words, if a file is being played by Stash or any other video player, the RenameFile plugin
|
||||
# will get an access denied error when trying to rename the file.
|
||||
# As a workaround, the 'handleExe' field can be populated with a full path to handle.exe or handle64.exe.
|
||||
# This executable can be downloaded from the following link:
|
||||
# https://learn.microsoft.com/en-us/sysinternals/downloads/handle
|
||||
# RenameFile can use the Handle.exe program to close all opened file handles by all processes before renaming the file.
|
||||
#
|
||||
# Warning: This feature can cause the process playing the video to crash.
|
||||
"handleExe": r"C:\Sysinternals\handle64.exe", # https://learn.microsoft.com/en-us/sysinternals/downloads/handle
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user