From c37d416b41ed940e84efc5e314c24a4dc6f75e6c Mon Sep 17 00:00:00 2001 From: dude-stash <322896044+dude-stash@users.noreply.github.com> Date: Sat, 17 Oct 2026 23:01:41 -0300 Subject: [PATCH 1/3] RenameFile: tolerate a null job queue job_queue() can return None, which crashed every taskQueue helper except tooManyScanOnTaskQueue. Normalise it to an empty list, and fix the missing f-string in the plugin-task job match. Co-Authored-By: Claude Sonnet 5.5 --- plugins/RenameFile/StashPluginHelper.py | 6 +++--- plugins/RenameFile/renamefile.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/RenameFile/StashPluginHelper.py b/plugins/RenameFile/StashPluginHelper.py index 9df93066..1992ad40 100644 --- a/plugins/RenameFile/StashPluginHelper.py +++ b/plugins/RenameFile/StashPluginHelper.py @@ -1127,8 +1127,8 @@ def mergeItems(self, fieldName, updateFieldName=None, listToAdd=[], NotStartWith class taskQueue: taskqueue = None def __init__(self, taskqueue): - self.taskqueue = taskqueue - + self.taskqueue = taskqueue or [] + def tooManyScanOnTaskQueue(self, tooManyQty = 5): count = 0 if self.taskqueue == None: @@ -1154,7 +1154,7 @@ def cleanGeneratedJobOnTaskQueue(self): def isRunningPluginTaskJobOnTaskQueue(self, taskName): for jobDetails in self.taskqueue: - if jobDetails['description'] == "Running plugin task: {taskName}": + if jobDetails['description'] == f"Running plugin task: {taskName}": return True return False diff --git a/plugins/RenameFile/renamefile.yml b/plugins/RenameFile/renamefile.yml index a022257c..ca3040e0 100644 --- a/plugins/RenameFile/renamefile.yml +++ b/plugins/RenameFile/renamefile.yml @@ -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: 1.0.3 +version: 1.0.4 url: https://discourse.stashapp.cc/t/renamefile/1334 ui: css: From 697a4299a545b3f77c0193d7fbafc5dbc2c2d24d Mon Sep 17 00:00:00 2001 From: dude-stash <322896044+dude-stash@users.noreply.github.com> Date: Sat, 17 Oct 2026 23:19:33 -0300 Subject: [PATCH 2/3] RenameFile: never overwrite or delete files, number colliding names rename()/move() silently overwrite an existing target, so two files of one scene that resolved to the same new name lost one of them. Pick a free 'name (N)' (checking associated files too) and drop both os.remove calls. Co-Authored-By: Claude Sonnet 5.5 --- plugins/RenameFile/renamefile.py | 43 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/plugins/RenameFile/renamefile.py b/plugins/RenameFile/renamefile.py index 139855f0..c56ef7c8 100644 --- a/plugins/RenameFile/renamefile.py +++ b/plugins/RenameFile/renamefile.py @@ -402,7 +402,24 @@ def rename_scene(scene_id): if original_file_name == newFilenameWithExt or original_file_name == new_filename: stash.Log(f"Nothing to do, because new file name matches original file name: (newFilenameWithExt={newFilenameWithExt})") return None - targetDidExist = True if os.path.isfile(new_file_path) else False + # Never overwrite: rename()/move() silently replace an existing file, so pick a free name with a sequential number + def target_taken(stem): + for ext in [Path(original_file_path).suffix] + (list(associated_files_to_rename) if rename_associated_files_enable else []): + candidate = f"{original_parent_directory}{os.sep}{stem}{ext}" + if os.path.lexists(candidate) and not (os.path.exists(original_file_path) and os.path.exists(candidate) and os.path.samefile(original_file_path, candidate)): + return True + return False + if target_taken(new_filename): + base_filename = new_filename + counter = 2 + while target_taken(f"{base_filename} ({counter})"): + counter += 1 + new_filename = f"{base_filename} ({counter})" + stash.Warn(f"Target name '{base_filename}' is already taken, using '{new_filename}' instead.") + newFilenameWithExt = new_filename + Path(original_file_path).suffix + new_file_path = f"{original_parent_directory}{os.sep}{new_filename}{Path(original_file_name).suffix}" + new_file_root_stem = f"{original_parent_directory}{os.sep}{new_filename}" + targetDidExist = False try: if openedfile != None: results = openedfile.closeFile(original_file_path) @@ -443,15 +460,8 @@ def rename_scene(scene_id): if not taskqueue.tooManyScanOnTaskQueue(maxScanCountDefault): stash.Trace(f"Calling [metadata_scan] for path {original_parent_directory.resolve().as_posix()}") stash.metadata_scan(paths=[original_parent_directory.resolve().as_posix()]) - if targetDidExist: - return None - if os.path.isfile(new_file_path): - if os.path.isfile(original_file_path): - os.remove(original_file_path) - pass - else: - # ToDo: Add delay rename here - return None + # Never delete files here, the original is left in place + return None if dry_run: stash.Log("Dry-Run, so skipping DB renaming") @@ -471,18 +481,7 @@ def rename_scene(scene_id): if not os.path.isfile(new_file_path): stash.Error(f"Failed to rename file from {scene_details['files'][0]['path']} to {new_file_path}.") elif os.path.isfile(scene_details['files'][0]['path']): - stash.Warn(f"Failed to rename file from {scene_details['files'][0]['path']} to {new_file_path}. Old file still exist. Will attempt delay deletion.") - for i in range(1, 5*60): - time.sleep(60) - if not os.path.isfile(new_file_path): - stash.Error(f"Not deleting old file name {original_file_path} because new file name (new_file_path) does NOT exist.") - break - os.remove(original_file_path) - if not os.path.isfile(original_file_path): - stash.Log(f"Deleted {original_file_path} in delay deletion after {i} minutes.") - stash.Trace(f"Calling [metadata_scan] for path {original_parent_directory.resolve().as_posix()}") - stash.metadata_scan(paths=[original_parent_directory.resolve().as_posix()]) - break + stash.Warn(f"Failed to rename file from {scene_details['files'][0]['path']} to {new_file_path}. Old file still exist and was left untouched.") else: org_stem = Path(scene_details['files'][0]['path']).stem new_stem = Path(new_file_path).stem From 3fbc1bc86057518cce7b18b77d09d8c301dd278d Mon Sep 17 00:00:00 2001 From: dude-stash <322896044+dude-stash@users.noreply.github.com> Date: Sat, 17 Oct 2026 23:25:12 -0300 Subject: [PATCH 3/3] RenameFile: respect the Max Tag Keys setting The limit lived in add_tag(), which nothing calls; getTags() joined every tag. Apply the limit (and -1 for none) in getTags(). Co-Authored-By: Claude Sonnet 5.5 --- plugins/RenameFile/renamefile.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/RenameFile/renamefile.py b/plugins/RenameFile/renamefile.py index c56ef7c8..9a3464dd 100644 --- a/plugins/RenameFile/renamefile.py +++ b/plugins/RenameFile/renamefile.py @@ -177,7 +177,10 @@ def getGalleries(scene, title): def getTags(scene, title): title = title.lower() results = "" + tags_added = 0 for tag in scene['tags']: + if max_tag_keys == -1 or (max_tag_keys is not None and tags_added >= int(max_tag_keys)): + break name = tag['name'] stash.Trace(f"tag = {name}") if excludeIgnoreAutoTags == True and tag['ignore_auto_tag'] == True: @@ -188,6 +191,7 @@ def getTags(scene, title): stash.Trace(f"Skipping tag name '{name}' because already in title: '{title}'") continue results += f"{name}, " + tags_added += 1 return results.strip(", ") # Function to form the new filename based on scene details and user settings