From 0729ac0a62dde9c392d4a3df03b7b30f6b522d8e Mon Sep 17 00:00:00 2001 From: santiagosayshey Date: Tue, 18 Feb 2025 01:49:38 +1030 Subject: [PATCH] feature: Include Format when Renaming (#143) - New: Field inside format general tab to enable include format in rename - New: Database migration that adds format renames table - New: Queries to get / update rename status for a format - Update: Format compiler checks for rename entries and add include rename field when found - Update: Parsing improvements for incoming commit messages --- backend/app/data/__init__.py | 74 +++++++++++++++++++ backend/app/db/__init__.py | 4 +- .../migrations/versions/002_format_renames.py | 23 ++++++ backend/app/db/queries/format_renames.py | 33 +++++++++ backend/app/importarr/format.py | 23 ++++-- .../components/format/FormatGeneralTab.jsx | 45 ++++++++--- .../src/components/format/FormatModal.jsx | 10 ++- frontend/src/components/format/FormatPage.jsx | 4 + .../settings/git/status/DiffCommit.jsx | 44 ++++++----- frontend/src/hooks/useFormatModal.js | 10 ++- 10 files changed, 230 insertions(+), 40 deletions(-) create mode 100644 backend/app/db/migrations/versions/002_format_renames.py create mode 100644 backend/app/db/queries/format_renames.py diff --git a/backend/app/data/__init__.py b/backend/app/data/__init__.py index 57871c7..0267b9d 100644 --- a/backend/app/data/__init__.py +++ b/backend/app/data/__init__.py @@ -6,6 +6,7 @@ from .utils import (get_category_directory, load_yaml_file, validate, save_yaml_file, update_yaml_file, get_file_modified_date, test_regex_pattern, test_format_conditions, check_delete_constraints, filename_to_display) +from ..db import add_format_to_renames, remove_format_from_renames, is_format_in_renames logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -28,6 +29,12 @@ def retrieve_all(category): file_path = os.path.join(directory, file_name) try: content = load_yaml_file(file_path) + # Add metadata for custom formats + if category == 'custom_format': + content['metadata'] = { + 'includeInRename': + is_format_in_renames(content['name']) + } result.append({ "file_name": file_name, @@ -69,6 +76,12 @@ def handle_item(category, name): if request.method == 'GET': try: content = load_yaml_file(file_path) + # Add metadata for custom formats + if category == 'custom_format': + content['metadata'] = { + 'includeInRename': + is_format_in_renames(content['name']) + } return jsonify({ "file_name": file_name, @@ -97,6 +110,22 @@ def handle_item(category, name): return jsonify({"error": error_message}), 409 try: + # If it's a custom format, remove from renames table first + if category == 'custom_format': + # Get the format name from the file before deleting it + content = load_yaml_file(file_path) + format_name = content.get('name') + if format_name: + # Check if it exists in renames before trying to remove + if is_format_in_renames(format_name): + remove_format_from_renames(format_name) + logger.info( + f"Removed {format_name} from renames table") + else: + logger.info( + f"{format_name} was not in renames table") + + # Then delete the file os.remove(file_path) return jsonify( {"message": f"Successfully deleted {file_name}"}), 200 @@ -116,11 +145,27 @@ def handle_item(category, name): if data and 'name' in data: data['name'] = data['name'].strip() + # Handle rename inclusion for custom formats + if category == 'custom_format': + include_in_rename = data.get('metadata', {}).get( + 'includeInRename', False) + # Remove metadata before saving YAML + if 'metadata' in data: + del data['metadata'] + if validate(data, category): + # Save YAML save_yaml_file(file_path, data, category) + + # If custom format, handle rename table + if category == 'custom_format' and include_in_rename: + add_format_to_renames(data['name']) + return jsonify( {"message": f"Successfully created {file_name}"}), 201 + return jsonify({"error": "Validation failed"}), 400 + except Exception as e: logger.error(f"Error creating file: {e}") return jsonify({"error": str(e)}), 500 @@ -131,15 +176,44 @@ def handle_item(category, name): try: data = request.get_json() + logger.info(f"Received PUT data for {name}: {data}") if data and 'name' in data: data['name'] = data['name'].strip() if data and 'rename' in data: data['rename'] = data['rename'].strip() + # Handle rename inclusion for custom formats + if category == 'custom_format': + include_in_rename = data.get('metadata', {}).get( + 'includeInRename', False) + + # Get current content to check for rename + current_content = load_yaml_file(file_path) + old_name = current_content.get('name') + new_name = data['name'] + + # Handle renames and toggles + if old_name != new_name and include_in_rename: + # Handle rename while keeping in table + remove_format_from_renames(old_name) + add_format_to_renames(new_name) + elif include_in_rename: + # Just turning it on + add_format_to_renames(new_name) + else: + # Turning it off + remove_format_from_renames(data['name']) + + # Remove metadata before saving YAML + if 'metadata' in data: + del data['metadata'] + + # Save YAML update_yaml_file(file_path, data, category) return jsonify( {"message": f"Successfully updated {file_name}"}), 200 + except Exception as e: logger.error(f"Error updating file: {e}") return jsonify({"error": str(e)}), 500 diff --git a/backend/app/db/__init__.py b/backend/app/db/__init__.py index 766a7c6..a8b4f80 100644 --- a/backend/app/db/__init__.py +++ b/backend/app/db/__init__.py @@ -2,9 +2,11 @@ from .connection import get_db from .queries.settings import get_settings, get_secret_key, save_settings from .queries.arr import get_unique_arrs +from .queries.format_renames import add_format_to_renames, remove_format_from_renames, is_format_in_renames from .migrations.runner import run_migrations __all__ = [ 'get_db', 'get_settings', 'get_secret_key', 'save_settings', - 'get_unique_arrs', 'run_migrations' + 'get_unique_arrs', 'run_migrations', 'add_format_to_renames', + 'remove_format_from_renames', 'is_format_in_renames' ] diff --git a/backend/app/db/migrations/versions/002_format_renames.py b/backend/app/db/migrations/versions/002_format_renames.py new file mode 100644 index 0000000..9d0d472 --- /dev/null +++ b/backend/app/db/migrations/versions/002_format_renames.py @@ -0,0 +1,23 @@ +# backend/app/db/migrations/versions/002_format_renames.py +from ...connection import get_db + +version = 2 +name = "format_renames" + + +def up(): + """Add table for tracking which formats to include in renames""" + with get_db() as conn: + conn.execute(''' + CREATE TABLE IF NOT EXISTS format_renames ( + format_name TEXT PRIMARY KEY NOT NULL + ) + ''') + conn.commit() + + +def down(): + """Remove the format_renames table""" + with get_db() as conn: + conn.execute('DROP TABLE IF EXISTS format_renames') + conn.commit() diff --git a/backend/app/db/queries/format_renames.py b/backend/app/db/queries/format_renames.py new file mode 100644 index 0000000..7ef018a --- /dev/null +++ b/backend/app/db/queries/format_renames.py @@ -0,0 +1,33 @@ +# backend/app/db/queries/format_renames.py +import logging +from ..connection import get_db + +logger = logging.getLogger(__name__) + + +def add_format_to_renames(format_name: str) -> None: + """Add a format to the renames table""" + with get_db() as conn: + conn.execute( + 'INSERT OR REPLACE INTO format_renames (format_name) VALUES (?)', + (format_name, )) + conn.commit() + logger.info(f"Added format to renames table: {format_name}") + + +def remove_format_from_renames(format_name: str) -> None: + """Remove a format from the renames table""" + with get_db() as conn: + conn.execute('DELETE FROM format_renames WHERE format_name = ?', + (format_name, )) + conn.commit() + logger.info(f"Removed format from renames table: {format_name}") + + +def is_format_in_renames(format_name: str) -> bool: + """Check if a format is in the renames table""" + with get_db() as conn: + result = conn.execute( + 'SELECT 1 FROM format_renames WHERE format_name = ?', + (format_name, )).fetchone() + return bool(result) diff --git a/backend/app/importarr/format.py b/backend/app/importarr/format.py index c48dfcd..3c133f5 100644 --- a/backend/app/importarr/format.py +++ b/backend/app/importarr/format.py @@ -6,6 +6,7 @@ from pathlib import Path from ..data.utils import (load_yaml_file, get_category_directory, REGEX_DIR, FORMAT_DIR) from ..compile import CustomFormat, FormatConverter, TargetApp +from ..db.queries.format_renames import is_format_in_renames logger = logging.getLogger('importarr') @@ -63,13 +64,21 @@ def import_formats_to_arr(format_names, base_url, api_key, arr_type, if not converted_format: raise ValueError("Format conversion failed") - # Use the potentially modified name (with [Dictionarry]) for arr - compiled_data = { - 'name': - format_name, # Use the possibly modified name - 'specifications': - [vars(spec) for spec in converted_format.specifications] - } + # Create base compiled data with ordered fields + compiled_data = {'name': format_name} # Start with name + + # Check rename status and add field right after name if true + if is_format_in_renames(original_name): + compiled_data['includeCustomFormatWhenRenaming'] = True + logger.info( + f"Format {original_name} has renames enabled, including field" + ) + + # Add specifications last + compiled_data['specifications'] = [ + vars(spec) for spec in converted_format.specifications + ] + logger.info("Compiled to:\n" + json.dumps([compiled_data], indent=2)) diff --git a/frontend/src/components/format/FormatGeneralTab.jsx b/frontend/src/components/format/FormatGeneralTab.jsx index e79ee11..04a9f39 100644 --- a/frontend/src/components/format/FormatGeneralTab.jsx +++ b/frontend/src/components/format/FormatGeneralTab.jsx @@ -8,10 +8,12 @@ const FormatGeneralTab = ({ description, tags, error, + includeInRename, onNameChange, onDescriptionChange, onAddTag, - onRemoveTag + onRemoveTag, + onIncludeInRenameChange }) => { const [newTag, setNewTag] = useState(''); @@ -41,13 +43,36 @@ const FormatGeneralTab = ({
{/* Name Input */}
-
- -

- Give your format a descriptive name -

+
+
+ +

+ Give your format a descriptive name +

+
+
+ +

+ Include this format's name in renamed files +

+
{ const tabs = [ {id: 'general', label: 'General'}, @@ -95,12 +97,14 @@ const FormatModal = ({ description={description} tags={tags} error={error} + includeInRename={includeInRename} onNameChange={onNameChange} onDescriptionChange={onDescriptionChange} onAddTag={tag => onTagsChange([...tags, tag])} onRemoveTag={tag => onTagsChange(tags.filter(t => t !== tag)) } + onIncludeInRenameChange={onIncludeInRenameChange} /> )} {activeTab === 'conditions' && ( @@ -145,7 +149,9 @@ FormatModal.propTypes = { onConditionsChange: PropTypes.func.isRequired, onTestsChange: PropTypes.func.isRequired, onActiveTabChange: PropTypes.func.isRequired, - onRunTests: PropTypes.func.isRequired + onRunTests: PropTypes.func.isRequired, + includeInRename: PropTypes.bool.isRequired, + onIncludeInRenameChange: PropTypes.func.isRequired }; export default FormatModal; diff --git a/frontend/src/components/format/FormatPage.jsx b/frontend/src/components/format/FormatPage.jsx index cccace5..55966ec 100644 --- a/frontend/src/components/format/FormatPage.jsx +++ b/frontend/src/components/format/FormatPage.jsx @@ -118,6 +118,7 @@ function FormatPage() { activeTab, isDeleting, isRunningTests, + includeInRename, setName, setDescription, setTags, @@ -125,6 +126,7 @@ function FormatPage() { setTests, setActiveTab, setIsDeleting, + setIncludeInRename, initializeForm, handleSave, handleRunTests, @@ -408,6 +410,8 @@ function FormatPage() { onRunTests={handleRunTests} onSave={handleSave} onDelete={handleDelete} + includeInRename={includeInRename} + onIncludeInRenameChange={setIncludeInRename} /> { const {subject, body} = commitMessage; + const renderLine = (line, index) => { + // Just handle basic bullet points (* or -) + if (line.startsWith('* ') || line.startsWith('- ')) { + return ( +
+ + {line.slice(2)} +
+ ); + } + + return ( +
+ {line} +
+ ); + }; + return (
@@ -31,18 +48,12 @@ const DiffCommit = ({commitMessage}) => { {body && ( @@ -53,11 +64,4 @@ const DiffCommit = ({commitMessage}) => { ); }; -DiffCommit.propTypes = { - commitMessage: PropTypes.shape({ - subject: PropTypes.string.isRequired, - body: PropTypes.string - }) -}; - export default DiffCommit; diff --git a/frontend/src/hooks/useFormatModal.js b/frontend/src/hooks/useFormatModal.js index dcd312f..c24dfe7 100644 --- a/frontend/src/hooks/useFormatModal.js +++ b/frontend/src/hooks/useFormatModal.js @@ -12,6 +12,7 @@ export const useFormatModal = (initialFormat, onSuccess) => { const [conditions, setConditions] = useState([]); const [tests, setTests] = useState([]); const [isCloning, setIsCloning] = useState(false); + const [includeInRename, setIncludeInRename] = useState(false); // Enhanced UI state with field-specific errors const [formErrors, setFormErrors] = useState({ @@ -78,6 +79,7 @@ export const useFormatModal = (initialFormat, onSuccess) => { setTags([]); setConditions([]); setTests([]); + setIncludeInRename(false); setFormErrors({name: '', conditions: '', tests: '', general: ''}); setIsDeleting(false); setIsCloning(false); @@ -92,6 +94,7 @@ export const useFormatModal = (initialFormat, onSuccess) => { setTags(format.tags || []); setConditions(format.conditions || []); setTests(format.tests || []); + setIncludeInRename(format.metadata?.includeInRename || false); setIsCloning(cloning || false); }, 0); } @@ -181,7 +184,10 @@ export const useFormatModal = (initialFormat, onSuccess) => { description, tags, conditions, - tests + tests, + metadata: { + includeInRename + } }; if (initialFormat && !isCloning) { @@ -284,6 +290,7 @@ export const useFormatModal = (initialFormat, onSuccess) => { tags, conditions, tests, + includeInRename, // UI state formErrors, activeTab, @@ -298,6 +305,7 @@ export const useFormatModal = (initialFormat, onSuccess) => { setTests, setActiveTab, setIsDeleting, + setIncludeInRename, // Main handlers initializeForm, handleSave,
-
- {body.split('\n').map((line, index) => ( -
- {line} -
- ))} +
+ {body + .split('\n') + .map((line, index) => + renderLine(line, index) + )}