CSFD integration
This commit is contained in:
273
src/ui/gui.py
273
src/ui/gui.py
@@ -9,12 +9,12 @@ from tkinter import ttk, simpledialog, messagebox, filedialog
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from src.core.media_utils import load_icon
|
||||
from src.ui.utils import load_icon
|
||||
from src.core.file_manager import FileManager
|
||||
from src.core.tag_manager import TagManager, DEFAULT_TAG_ORDER
|
||||
from src.core.file import File
|
||||
from src.core.tag import Tag
|
||||
from src.core.list_manager import ListManager
|
||||
# ListManager removed - sorting implemented directly in GUI
|
||||
from src.core.constants import APP_NAME, VERSION, APP_VIEWPORT
|
||||
from src.core.config import save_global_config
|
||||
from src.core.hardlink_manager import HardlinkManager
|
||||
@@ -219,8 +219,6 @@ class App:
|
||||
def __init__(self, filehandler: FileManager, tagmanager: TagManager):
|
||||
self.filehandler = filehandler
|
||||
self.tagmanager = tagmanager
|
||||
self.list_manager = ListManager()
|
||||
|
||||
# State
|
||||
self.states = {}
|
||||
self.file_items = {} # Treeview item_id -> File object mapping
|
||||
@@ -231,6 +229,7 @@ class App:
|
||||
self.sort_mode = "name"
|
||||
self.sort_order = "asc"
|
||||
self.category_colors = {} # category -> color mapping
|
||||
self.show_csfd_column = True # CSFD column visibility
|
||||
|
||||
self.filehandler.on_files_changed = self.update_files_from_manager
|
||||
|
||||
@@ -312,6 +311,7 @@ class App:
|
||||
# File menu
|
||||
file_menu = tk.Menu(menu_bar, tearoff=0)
|
||||
file_menu.add_command(label="Open Folder... (Ctrl+O)", command=self.open_folder_dialog)
|
||||
file_menu.add_command(label="Zavřít složku (Ctrl+W)", command=self.close_folder)
|
||||
file_menu.add_command(label="Nastavit ignorované vzory", command=self.set_ignore_patterns)
|
||||
file_menu.add_separator()
|
||||
file_menu.add_command(label="Exit (Ctrl+Q)", command=self.root.quit)
|
||||
@@ -323,6 +323,12 @@ class App:
|
||||
variable=self.hide_ignored_var,
|
||||
command=self.toggle_hide_ignored
|
||||
)
|
||||
self.show_csfd_var = tk.BooleanVar(value=True, master=self.root)
|
||||
view_menu.add_checkbutton(
|
||||
label="Zobrazit CSFD sloupec",
|
||||
variable=self.show_csfd_var,
|
||||
command=self.toggle_csfd_column
|
||||
)
|
||||
view_menu.add_command(label="Refresh (F5)", command=self.refresh_all)
|
||||
|
||||
# Tools menu
|
||||
@@ -331,6 +337,9 @@ class App:
|
||||
tools_menu.add_command(label="Detekovat rozlišení videí", command=self.detect_video_resolution)
|
||||
tools_menu.add_command(label="Přiřadit tagy (Ctrl+T)", command=self.assign_tag_to_selected_bulk)
|
||||
tools_menu.add_separator()
|
||||
tools_menu.add_command(label="Nastavit CSFD URL...", command=self.set_csfd_url_for_selected)
|
||||
tools_menu.add_command(label="Načíst tagy z CSFD", command=self.apply_csfd_tags_for_selected)
|
||||
tools_menu.add_separator()
|
||||
tools_menu.add_command(label="Nastavit hardlink složku...", command=self.configure_hardlink_folder)
|
||||
tools_menu.add_command(label="Aktualizovat hardlink strukturu", command=self.update_hardlink_structure)
|
||||
tools_menu.add_command(label="Vytvořit hardlink strukturu...", command=self.create_hardlink_structure)
|
||||
@@ -428,22 +437,27 @@ class App:
|
||||
table_frame = tk.Frame(file_frame)
|
||||
table_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
# Define columns
|
||||
columns = ("name", "date", "tags", "size")
|
||||
# Define columns (including CSFD)
|
||||
columns = ("name", "date", "tags", "csfd", "size")
|
||||
self.file_table = ttk.Treeview(table_frame, columns=columns, show="headings", selectmode="extended")
|
||||
|
||||
# Column headers with sort commands
|
||||
self.file_table.heading("name", text="📄 Název ▲", command=lambda: self.sort_by_column("name"))
|
||||
self.file_table.heading("date", text="📅 Datum", command=lambda: self.sort_by_column("date"))
|
||||
self.file_table.heading("tags", text="🏷️ Štítky")
|
||||
self.file_table.heading("csfd", text="🎬 CSFD")
|
||||
self.file_table.heading("size", text="💾 Velikost", command=lambda: self.sort_by_column("size"))
|
||||
|
||||
# Column widths
|
||||
self.file_table.column("name", width=300)
|
||||
self.file_table.column("date", width=100)
|
||||
self.file_table.column("tags", width=200)
|
||||
self.file_table.column("csfd", width=50)
|
||||
self.file_table.column("size", width=80)
|
||||
|
||||
# Load CSFD column visibility from folder config
|
||||
self._update_csfd_column_visibility()
|
||||
|
||||
# Scrollbars
|
||||
vsb = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.file_table.yview)
|
||||
hsb = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=self.file_table.xview)
|
||||
@@ -493,6 +507,7 @@ class App:
|
||||
# Tag context menu
|
||||
self.tag_menu = tk.Menu(self.root, tearoff=0)
|
||||
self.tag_menu.add_command(label="Nový štítek", command=self.tree_add_tag)
|
||||
self.tag_menu.add_command(label="Přejmenovat štítek", command=self.tree_rename_tag)
|
||||
self.tag_menu.add_command(label="Smazat štítek", command=self.tree_delete_tag)
|
||||
|
||||
# File context menu
|
||||
@@ -501,11 +516,15 @@ class App:
|
||||
self.file_menu.add_command(label="Přiřadit štítky (Ctrl+T)", command=self.assign_tag_to_selected_bulk)
|
||||
self.file_menu.add_command(label="Nastavit datum (Ctrl+D)", command=self.set_date_for_selected)
|
||||
self.file_menu.add_separator()
|
||||
self.file_menu.add_command(label="Nastavit CSFD URL...", command=self.set_csfd_url_for_selected)
|
||||
self.file_menu.add_command(label="Načíst tagy z CSFD", command=self.apply_csfd_tags_for_selected)
|
||||
self.file_menu.add_separator()
|
||||
self.file_menu.add_command(label="Smazat z indexu (Del)", command=self.remove_selected_files)
|
||||
|
||||
def _bind_shortcuts(self):
|
||||
"""Bind keyboard shortcuts"""
|
||||
self.root.bind("<Control-o>", lambda e: self.open_folder_dialog())
|
||||
self.root.bind("<Control-w>", lambda e: self.close_folder())
|
||||
self.root.bind("<Control-q>", lambda e: self.root.quit())
|
||||
self.root.bind("<Control-t>", lambda e: self.assign_tag_to_selected_bulk())
|
||||
self.root.bind("<Control-d>", lambda e: self.set_date_for_selected())
|
||||
@@ -570,6 +589,9 @@ class App:
|
||||
self.tag_tree.tag_configure(f"cat_{category}", foreground=color)
|
||||
self.tag_tree.tag_configure(f"tag_{category}", foreground=color)
|
||||
|
||||
# Force tree update
|
||||
self.tag_tree.update_idletasks()
|
||||
|
||||
def update_tag_counts(self, filtered_files):
|
||||
"""Update tag counts in sidebar based on filtered files"""
|
||||
if not hasattr(self, 'tag_tree_items'):
|
||||
@@ -669,6 +691,134 @@ class App:
|
||||
self.update_files_from_manager(self.filehandler.filelist)
|
||||
self.status_label.config(text=f"Smazán tag: {name}")
|
||||
|
||||
def tree_rename_tag(self):
|
||||
"""Rename selected tag or category"""
|
||||
item = self.selected_tree_item_for_context
|
||||
if not item:
|
||||
return
|
||||
|
||||
# Don't allow renaming root
|
||||
if item == self.root_tag_id:
|
||||
return
|
||||
|
||||
parent_id = self.tag_tree.parent(item)
|
||||
current_text = self.tag_tree.item(item, "text").strip()
|
||||
|
||||
# Check if this is a category (parent is root) or a tag
|
||||
is_category = (parent_id == self.root_tag_id)
|
||||
|
||||
if is_category:
|
||||
# Renaming a category
|
||||
current_name = current_text.replace("📁 ", "")
|
||||
new_name = simpledialog.askstring(
|
||||
"Přejmenovat kategorii",
|
||||
f"Nový název kategorie '{current_name}':",
|
||||
initialvalue=current_name
|
||||
)
|
||||
if not new_name or new_name == current_name:
|
||||
return
|
||||
|
||||
# Check if new name already exists - offer merge
|
||||
if new_name in self.tagmanager.get_categories():
|
||||
merge = messagebox.askyesno(
|
||||
"Kategorie existuje",
|
||||
f"Kategorie '{new_name}' již existuje.\n\n"
|
||||
f"Chcete sloučit kategorii '{current_name}' do '{new_name}'?\n\n"
|
||||
f"Všechny štítky z '{current_name}' budou přesunuty do '{new_name}'.",
|
||||
icon="question"
|
||||
)
|
||||
if not merge:
|
||||
return
|
||||
|
||||
# Merge category in all files
|
||||
updated_count = self.filehandler.merge_category_in_files(current_name, new_name)
|
||||
|
||||
# Refresh sidebar
|
||||
self.refresh_sidebar()
|
||||
self.root.update_idletasks()
|
||||
self.update_files_from_manager(self.filehandler.filelist)
|
||||
|
||||
self.status_label.config(
|
||||
text=f"Kategorie sloučena: {current_name} → {new_name} ({updated_count} souborů)"
|
||||
)
|
||||
return
|
||||
|
||||
# Rename category in all files
|
||||
updated_count = self.filehandler.rename_category_in_files(current_name, new_name)
|
||||
|
||||
# Refresh sidebar
|
||||
self.refresh_sidebar()
|
||||
self.root.update_idletasks()
|
||||
self.update_files_from_manager(self.filehandler.filelist)
|
||||
|
||||
self.status_label.config(
|
||||
text=f"Kategorie přejmenována: {current_name} → {new_name} ({updated_count} souborů)"
|
||||
)
|
||||
else:
|
||||
# Renaming a tag
|
||||
# Get tag name (without count suffix)
|
||||
# Find the tag name from the mapping
|
||||
tag_name = None
|
||||
for full_path, (item_id, name) in self.tag_tree_items.items():
|
||||
if item_id == item:
|
||||
tag_name = name
|
||||
break
|
||||
|
||||
if tag_name is None:
|
||||
# Fallback: parse from text (remove leading spaces and count)
|
||||
tag_name = current_text.lstrip()
|
||||
# Remove count suffix like " (5)"
|
||||
import re
|
||||
tag_name = re.sub(r'\s*\(\d+\)\s*$', '', tag_name)
|
||||
|
||||
category = self.tag_tree.item(parent_id, "text").replace("📁 ", "")
|
||||
|
||||
new_name = simpledialog.askstring(
|
||||
"Přejmenovat štítek",
|
||||
f"Nový název štítku '{tag_name}':",
|
||||
initialvalue=tag_name
|
||||
)
|
||||
if not new_name or new_name == tag_name:
|
||||
return
|
||||
|
||||
# Check if new name already exists in this category - offer merge
|
||||
existing_tags = [t.name for t in self.tagmanager.get_tags_in_category(category)]
|
||||
if new_name in existing_tags:
|
||||
merge = messagebox.askyesno(
|
||||
"Štítek existuje",
|
||||
f"Štítek '{new_name}' v kategorii '{category}' již existuje.\n\n"
|
||||
f"Chcete sloučit '{tag_name}' do '{new_name}'?\n\n"
|
||||
f"Všechny soubory s '{tag_name}' budou mít tento štítek nahrazen za '{new_name}'.",
|
||||
icon="question"
|
||||
)
|
||||
if not merge:
|
||||
return
|
||||
|
||||
# Merge tag in all files
|
||||
updated_count = self.filehandler.merge_tag_in_files(category, tag_name, new_name)
|
||||
|
||||
# Refresh sidebar
|
||||
self.refresh_sidebar()
|
||||
self.root.update_idletasks()
|
||||
self.update_files_from_manager(self.filehandler.filelist)
|
||||
|
||||
self.status_label.config(
|
||||
text=f"Štítek sloučen: {category}/{tag_name} → {category}/{new_name} ({updated_count} souborů)"
|
||||
)
|
||||
return
|
||||
|
||||
# Rename tag in all files
|
||||
updated_count = self.filehandler.rename_tag_in_files(category, tag_name, new_name)
|
||||
|
||||
# Refresh sidebar
|
||||
self.refresh_sidebar()
|
||||
self.root.update_idletasks()
|
||||
self.update_files_from_manager(self.filehandler.filelist)
|
||||
|
||||
self.status_label.config(
|
||||
text=f"Štítek přejmenován: {category}/{tag_name} → {category}/{new_name} ({updated_count} souborů)"
|
||||
)
|
||||
|
||||
def get_checked_tags(self) -> List[Tag]:
|
||||
"""Get list of checked tags"""
|
||||
tags = []
|
||||
@@ -740,13 +890,16 @@ class App:
|
||||
if len(f.tags) > 3:
|
||||
tags += f" +{len(f.tags) - 3}"
|
||||
|
||||
# CSFD indicator
|
||||
csfd = "✓" if f.csfd_url else ""
|
||||
|
||||
try:
|
||||
size = f.file_path.stat().st_size
|
||||
size_str = self._format_size(size)
|
||||
except:
|
||||
size_str = "?"
|
||||
|
||||
item_id = self.file_table.insert("", "end", values=(name, date, tags, size_str))
|
||||
item_id = self.file_table.insert("", "end", values=(name, date, tags, csfd, size_str))
|
||||
self.file_items[item_id] = f
|
||||
|
||||
# Update status
|
||||
@@ -838,11 +991,27 @@ class App:
|
||||
f.tagmanager.add_tag(t.category, t.name)
|
||||
|
||||
self.status_label.config(text=f"Přidána složka: {folder_path}")
|
||||
self._update_csfd_column_visibility() # Load CSFD column setting for new folder
|
||||
self.refresh_sidebar()
|
||||
self.update_files_from_manager(self.filehandler.filelist)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Chyba", f"Nelze přidat složku {folder}: {e}")
|
||||
|
||||
def close_folder(self):
|
||||
"""Close current folder safely"""
|
||||
if not self.filehandler.current_folder:
|
||||
self.status_label.config(text="Žádná složka není otevřena")
|
||||
return
|
||||
|
||||
folder_name = self.filehandler.current_folder.name
|
||||
|
||||
# Close folder (saves metadata and clears state)
|
||||
self.filehandler.close_folder()
|
||||
|
||||
# Refresh UI
|
||||
self.refresh_sidebar()
|
||||
self.status_label.config(text=f"Složka zavřena: {folder_name}")
|
||||
|
||||
def open_selected_files(self):
|
||||
"""Open selected files"""
|
||||
files = self.get_selected_files()
|
||||
@@ -974,6 +1143,96 @@ class App:
|
||||
self.show_full_path = not self.show_full_path
|
||||
self.update_files_from_manager(self.filehandler.filelist)
|
||||
|
||||
def toggle_csfd_column(self):
|
||||
"""Toggle CSFD column visibility"""
|
||||
self.show_csfd_column = self.show_csfd_var.get()
|
||||
self._update_csfd_column_visibility()
|
||||
|
||||
# Save to folder config
|
||||
if self.filehandler.current_folder:
|
||||
folder_config = self.filehandler.get_folder_config()
|
||||
folder_config["show_csfd_column"] = self.show_csfd_column
|
||||
self.filehandler.save_folder_config(config=folder_config)
|
||||
|
||||
def _update_csfd_column_visibility(self):
|
||||
"""Update CSFD column width based on visibility setting"""
|
||||
# Load from folder config if available
|
||||
if self.filehandler.current_folder:
|
||||
folder_config = self.filehandler.get_folder_config()
|
||||
self.show_csfd_column = folder_config.get("show_csfd_column", True)
|
||||
if hasattr(self, 'show_csfd_var'):
|
||||
self.show_csfd_var.set(self.show_csfd_column)
|
||||
|
||||
# Update column width
|
||||
if hasattr(self, 'file_table'):
|
||||
if self.show_csfd_column:
|
||||
self.file_table.column("csfd", width=50)
|
||||
else:
|
||||
self.file_table.column("csfd", width=0)
|
||||
|
||||
def set_csfd_url_for_selected(self):
|
||||
"""Set CSFD URL for selected files"""
|
||||
files = self.get_selected_files()
|
||||
if not files:
|
||||
self.status_label.config(text="Nebyly vybrány žádné soubory")
|
||||
return
|
||||
|
||||
# Get current URL from first file
|
||||
current_url = files[0].csfd_url or ""
|
||||
|
||||
prompt = "Zadej CSFD URL (např. https://www.csfd.cz/film/9423-pane-vy-jste-vdova/):"
|
||||
url = simpledialog.askstring("Nastavit CSFD URL", prompt, initialvalue=current_url, parent=self.root)
|
||||
if url is None:
|
||||
return
|
||||
|
||||
for f in files:
|
||||
f.set_csfd_url(url if url != "" else None)
|
||||
|
||||
self.update_files_from_manager(self.filehandler.filelist)
|
||||
self.status_label.config(text=f"CSFD URL nastaveno pro {len(files)} soubor(ů)")
|
||||
|
||||
def apply_csfd_tags_for_selected(self):
|
||||
"""Load tags from CSFD for selected files"""
|
||||
files = self.get_selected_files()
|
||||
if not files:
|
||||
self.status_label.config(text="Nebyly vybrány žádné soubory")
|
||||
return
|
||||
|
||||
# Filter files with CSFD URL
|
||||
files_with_url = [f for f in files if f.csfd_url]
|
||||
if not files_with_url:
|
||||
messagebox.showwarning("Upozornění", "Žádný z vybraných souborů nemá nastavenou CSFD URL")
|
||||
return
|
||||
|
||||
self.status_label.config(text=f"Načítám tagy z CSFD pro {len(files_with_url)} souborů...")
|
||||
self.root.update()
|
||||
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
all_tags_added = []
|
||||
|
||||
for f in files_with_url:
|
||||
result = f.apply_csfd_tags()
|
||||
if result["success"]:
|
||||
success_count += 1
|
||||
all_tags_added.extend(result["tags_added"])
|
||||
else:
|
||||
error_count += 1
|
||||
|
||||
# Refresh sidebar to show new categories
|
||||
self.refresh_sidebar()
|
||||
self.root.update_idletasks() # Force UI refresh
|
||||
self.update_files_from_manager(self.filehandler.filelist)
|
||||
|
||||
# Show result
|
||||
if error_count > 0:
|
||||
messagebox.showwarning("Dokončeno s chybami",
|
||||
f"Úspěšně: {success_count}, Chyby: {error_count}\n"
|
||||
f"Přidáno {len(all_tags_added)} tagů")
|
||||
else:
|
||||
self.status_label.config(
|
||||
text=f"Načteno z CSFD: {success_count} souborů, přidáno {len(all_tags_added)} tagů")
|
||||
|
||||
def sort_by_column(self, column: str):
|
||||
"""Sort by column header click"""
|
||||
if self.sort_mode == column:
|
||||
|
||||
Reference in New Issue
Block a user