Update engine.py

This commit is contained in:
2026-01-17 15:32:43 +01:00
parent fbf11fd7b1
commit acebc54c3d

117
engine.py
View File

@@ -14,7 +14,7 @@ class SorterEngine:
conn = sqlite3.connect(SorterEngine.DB_PATH) conn = sqlite3.connect(SorterEngine.DB_PATH)
cursor = conn.cursor() cursor = conn.cursor()
# Profile table supports independent paths for each tab # Profile table supports 7 columns for independent paths and naming mode
cursor.execute('''CREATE TABLE IF NOT EXISTS profiles cursor.execute('''CREATE TABLE IF NOT EXISTS profiles
(name TEXT PRIMARY KEY, (name TEXT PRIMARY KEY,
tab1_target TEXT, tab1_target TEXT,
@@ -22,13 +22,10 @@ class SorterEngine:
tab4_source TEXT, tab4_out TEXT, tab4_source TEXT, tab4_out TEXT,
mode TEXT)''') mode TEXT)''')
# Maps source paths to persistent numeric IDs
cursor.execute('''CREATE TABLE IF NOT EXISTS folder_ids (path TEXT PRIMARY KEY, folder_id INTEGER)''') cursor.execute('''CREATE TABLE IF NOT EXISTS folder_ids (path TEXT PRIMARY KEY, folder_id INTEGER)''')
# Stores sorting subfolder names for Tab 4
cursor.execute('''CREATE TABLE IF NOT EXISTS categories (name TEXT PRIMARY KEY)''') cursor.execute('''CREATE TABLE IF NOT EXISTS categories (name TEXT PRIMARY KEY)''')
# Seed default categories from the original script if empty # Seed default categories if empty
cursor.execute("SELECT COUNT(*) FROM categories") cursor.execute("SELECT COUNT(*) FROM categories")
if cursor.fetchone()[0] == 0: if cursor.fetchone()[0] == 0:
for cat in ["_TRASH", "Default", "Action", "Solo"]: for cat in ["_TRASH", "Default", "Action", "Solo"]:
@@ -43,7 +40,6 @@ class SorterEngine:
"""Updates specific tab paths while preserving others in the DB.""" """Updates specific tab paths while preserving others in the DB."""
conn = sqlite3.connect(SorterEngine.DB_PATH) conn = sqlite3.connect(SorterEngine.DB_PATH)
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("SELECT * FROM profiles WHERE name = ?", (profile_name,)) cursor.execute("SELECT * FROM profiles WHERE name = ?", (profile_name,))
row = cursor.fetchone() row = cursor.fetchone()
@@ -59,7 +55,6 @@ class SorterEngine:
t4_o if t4_o is not None else row[5], t4_o if t4_o is not None else row[5],
mode if mode is not None else row[6] mode if mode is not None else row[6]
) )
cursor.execute("INSERT OR REPLACE INTO profiles VALUES (?, ?, ?, ?, ?, ?, ?)", new_values) cursor.execute("INSERT OR REPLACE INTO profiles VALUES (?, ?, ?, ?, ?, ?, ?)", new_values)
conn.commit() conn.commit()
conn.close() conn.close()
@@ -75,55 +70,7 @@ class SorterEngine:
return {r[0]: {"tab1_target": r[1], "tab2_target": r[2], "tab2_control": r[3], return {r[0]: {"tab1_target": r[1], "tab2_target": r[2], "tab2_control": r[3],
"tab4_source": r[4], "tab4_out": r[5], "mode": r[6]} for r in rows} "tab4_source": r[4], "tab4_out": r[5], "mode": r[6]} for r in rows}
@staticmethod # --- IMAGE & ID SCANNING (CRITICAL) ---
def delete_profile(name):
"""Removes a workspace profile."""
conn = sqlite3.connect(SorterEngine.DB_PATH)
cursor = conn.cursor()
cursor.execute("DELETE FROM profiles WHERE name = ?", (name,))
conn.commit()
conn.close()
# --- FOLDER ID & CATEGORY LOGIC ---
@staticmethod
def get_folder_id(source_path):
"""Retrieves or generates a persistent ID for a specific folder."""
conn = sqlite3.connect(SorterEngine.DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT folder_id FROM folder_ids WHERE path = ?", (source_path,))
result = cursor.fetchone()
if result:
fid = result[0]
else:
cursor.execute("SELECT MAX(folder_id) FROM folder_ids")
row = cursor.fetchone()
fid = (row[0] + 1) if row and row[0] else 1
cursor.execute("INSERT INTO folder_ids VALUES (?, ?)", (source_path, fid))
conn.commit()
conn.close()
return fid
@staticmethod
def get_categories():
"""Fetches sorting categories for Tab 4 buttons."""
conn = sqlite3.connect(SorterEngine.DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT name FROM categories ORDER BY name ASC")
cats = [r[0] for r in cursor.fetchall()]
conn.close()
return cats
@staticmethod
def add_category(name):
"""Adds a new persistent category to the SQL database."""
conn = sqlite3.connect(SorterEngine.DB_PATH)
cursor = conn.cursor()
cursor.execute("INSERT OR IGNORE INTO categories VALUES (?)", (name,))
conn.commit()
conn.close()
# --- FILE & IMAGE OPERATIONS ---
@staticmethod @staticmethod
def get_images(path): def get_images(path):
"""Standard image scanner for directories.""" """Standard image scanner for directories."""
@@ -131,6 +78,19 @@ class SorterEngine:
if not path or not os.path.exists(path): return [] if not path or not os.path.exists(path): return []
return sorted([f for f in os.listdir(path) if f.lower().endswith(exts)]) return sorted([f for f in os.listdir(path) if f.lower().endswith(exts)])
@staticmethod
def get_id_mapping(path):
"""Maps idXXX prefixes to lists of filenames to handle collisions."""
mapping = {}
images = SorterEngine.get_images(path)
for f in images:
if f.startswith("id") and "_" in f:
prefix = f.split('_')[0]
if prefix not in mapping:
mapping[prefix] = []
mapping[prefix].append(f)
return mapping
@staticmethod @staticmethod
def get_max_id_number(target_path): def get_max_id_number(target_path):
"""Finds the highest idXXX_ prefix in a directory.""" """Finds the highest idXXX_ prefix in a directory."""
@@ -144,6 +104,25 @@ class SorterEngine:
except: continue except: continue
return max_id return max_id
@staticmethod
def get_folder_id(source_path):
"""Retrieves or generates a persistent ID for a specific folder."""
conn = sqlite3.connect(SorterEngine.DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT folder_id FROM folder_ids WHERE path = ?", (source_path,))
result = cursor.fetchone()
if result:
fid = result[0]
else:
cursor.execute("SELECT MAX(folder_id) FROM folder_ids")
row = cursor.fetchone()
fid = (row[0] + 1) if row and row[0] else 1
cursor.execute("INSERT INTO folder_ids VALUES (?, ?)", (source_path, fid))
conn.commit()
conn.close()
return fid
# --- FILE MANIPULATION ---
@staticmethod @staticmethod
def harmonize_names(t_p, c_p): def harmonize_names(t_p, c_p):
"""Forces the control file to match the target name.""" """Forces the control file to match the target name."""
@@ -168,7 +147,7 @@ class SorterEngine:
@staticmethod @staticmethod
def move_to_unused_synced(t_p, c_p, t_root, c_root): def move_to_unused_synced(t_p, c_p, t_root, c_root):
"""Moves a pair to 'unused' subfolders with matched names.""" """Moves a pair to 'unused' subfolders."""
t_name = os.path.basename(t_p) t_name = os.path.basename(t_p)
t_un = os.path.join(t_root, "unused", t_name) t_un = os.path.join(t_root, "unused", t_name)
c_un = os.path.join(c_root, "unused", t_name) c_un = os.path.join(c_root, "unused", t_name)
@@ -190,9 +169,25 @@ class SorterEngine:
shutil.move(c_p, c_dst) shutil.move(c_p, c_dst)
return t_dst, c_dst return t_dst, c_dst
@staticmethod
def get_categories():
conn = sqlite3.connect(SorterEngine.DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT name FROM categories ORDER BY name ASC")
cats = [r[0] for r in cursor.fetchall()]
conn.close()
return cats
@staticmethod
def add_category(name):
conn = sqlite3.connect(SorterEngine.DB_PATH)
cursor = conn.cursor()
cursor.execute("INSERT OR IGNORE INTO categories VALUES (?)", (name,))
conn.commit()
conn.close()
@staticmethod @staticmethod
def compress_for_web(path, quality): def compress_for_web(path, quality):
"""Converts images to compressed JPEGs for the UI."""
try: try:
with Image.open(path) as img: with Image.open(path) as img:
buf = BytesIO() buf = BytesIO()
@@ -202,9 +197,7 @@ class SorterEngine:
@staticmethod @staticmethod
def revert_action(action): def revert_action(action):
"""Undoes the last move operation.""" if action['type'] in ['unused', 'cat_move', 'move']:
if action['type'] in ['link_standard', 'link_solo', 'unused', 'cat_move', 'move']:
if os.path.exists(action['t_dst']): shutil.move(action['t_dst'], action['t_src']) if os.path.exists(action['t_dst']): shutil.move(action['t_dst'], action['t_src'])
if 'c_dst' in action and os.path.exists(action['c_dst']): if 'c_dst' in action and os.path.exists(action['c_dst']):
if 'link' in action['type']: os.remove(action['c_dst']) shutil.move(action['c_dst'], action['c_src'])
else: shutil.move(action['c_dst'], action['c_src'])