From 5571e18102803880618e6935a8784343b96ce2ff Mon Sep 17 00:00:00 2001 From: Kapitan Date: Wed, 26 Feb 2025 22:41:56 +0100 Subject: [PATCH] Better UI, edit tags --- kapitanbooru_uploader/ImageBrowser.py | 414 +++++++++++++++++++------- kapitanbooru_uploader/autocomplete.py | 14 +- pyproject.toml | 2 +- 3 files changed, 319 insertions(+), 111 deletions(-) diff --git a/kapitanbooru_uploader/ImageBrowser.py b/kapitanbooru_uploader/ImageBrowser.py index eb4277d..9e7e23e 100644 --- a/kapitanbooru_uploader/ImageBrowser.py +++ b/kapitanbooru_uploader/ImageBrowser.py @@ -1,64 +1,96 @@ -from .common import open_tag_wiki_url, open_webbrowser -from .ProgressFile import ProgressFile -from .autocomplete import TagManager -from .settings import Settings -from .tag_processing import TAG_FIXES, parse_parameters, process_tag -from .tagger_cache import TaggerCache -from .TagsRepo import TagsRepo - - -import networkx as nx -import requests -import wdtagger as wdt -from PIL import Image, ImageTk, PngImagePlugin - - import glob import hashlib +import inspect import os +import queue import threading import tkinter as tk from tkinter import filedialog, messagebox, ttk from typing import Tuple +import networkx as nx +import requests +from PIL import Image, ImageTk, PngImagePlugin + +import wdtagger as wdt +from .ProgressFile import ProgressFile +from .TagsRepo import TagsRepo +from .autocomplete import TagManager +from .common import get_auth_token, login, open_tag_wiki_url, open_webbrowser +from .settings import Settings +from .tag_processing import TAG_FIXES, parse_parameters, process_tag +from .tagger_cache import TaggerCache + class ProcessingDialog: def __init__(self, root, target_function, *args): self.root = root - self.top = tk.Toplevel(root) # Create a top-level window + self.top = tk.Toplevel(root) self.top.title("Processing...") self.top.geometry("300x150") - self.top.protocol("WM_DELETE_WINDOW", self.on_close) # Handle close event + self.top.protocol("WM_DELETE_WINDOW", self.on_close) - # Create Label and Progress Bar (or rotating animation) self.label = tk.Label(self.top, text="Processing, please wait...") self.label.pack(pady=10) + # Start with indeterminate progress bar self.progress = ttk.Progressbar(self.top, mode="indeterminate") self.progress.pack(pady=10, fill="x") - self.progress.start(10) # Start animation + self.progress.start(10) - # Create a thread for the target function + # Setup communication queue and periodic checker + self.queue = queue.Queue() self.running = True self.thread = threading.Thread( target=self.run_task, args=(target_function, *args) ) self.thread.start() + self.top.after(100, self.process_queue) + + def process_queue(self): + """Process messages from the background thread""" + while self.running: + try: + msg = self.queue.get_nowait() + + if msg[0] == "mode": + self.progress.config(mode=msg[1]) + if msg[1] == "determinate": + self.progress["value"] = 0 + elif msg[0] == "max": + self.progress["maximum"] = msg[1] + elif msg[0] == "progress": + self.progress["value"] = msg[1] + elif msg[0] == "label": + self.label.config(text=msg[1]) + + self.top.update_idletasks() + except queue.Empty: + break + + if self.running: + self.top.after(100, self.process_queue) def run_task(self, target_function, *args): + """Execute target function with progress queue if supported""" try: - target_function(*args) # Run the function + sig = inspect.signature(target_function) + if "progress_queue" in sig.parameters: + target_function(*args, progress_queue=self.queue) + else: + target_function(*args) finally: - self.close_dialog() # Close when done + self.close_dialog() def close_dialog(self): - """Safely close the dialog.""" + """Safely close the dialog""" if self.running: self.running = False - self.top.after(0, self.top.destroy) # Ensure UI updates on the main thread + self.progress.stop() + self.top.after(0, self.top.destroy) def on_close(self): - """User manually closed the dialog, terminate thread.""" + """Handle manual window closure""" self.running = False self.top.destroy() @@ -271,12 +303,42 @@ class ImageBrowser(tk.Tk): def create_menu(self): menubar = tk.Menu(self) self.config(menu=menubar) - menubar.add_command(label="Ustawienia", command=self.open_settings) - menubar.add_command(label="Wyczyść cache Taggera", command=self.clear_cache) - menubar.add_command(label="Wrzuć wszystko", command=self.upload_all_files) - menubar.add_command( + + # Create file menu and store it as instance variable + self.file_menu = tk.Menu(menubar, tearoff=0) + + # File menu items - create references for items we need to control + self.file_menu.add_command(label="Otwórz folder", command=self.select_folder) + self.file_menu.add_separator() + self.file_menu.add_command( + label="Wyślij", command=self.upload_current_image, state=tk.DISABLED + ) + self.file_menu.add_command( + label="Wyślij wszystko", command=self.upload_all_files, state=tk.DISABLED + ) + self.file_menu.add_separator() + self.file_menu.add_command( + label="Podmień tagi", command=self.edit_current_image, state=tk.DISABLED + ) + self.file_menu.add_command( + label="Otwórz post", command=self.view_current_post, state=tk.DISABLED + ) + self.file_menu.add_separator() + self.file_menu.add_command(label="Zakończ", command=self.quit) + + menubar.add_cascade(label="Plik", menu=self.file_menu) + + # Options menu + options_menu = tk.Menu(menubar, tearoff=0) + options_menu.add_command(label="Ustawienia", command=self.open_settings) + options_menu.add_separator() + options_menu.add_command( + label="Wyczyść cache Taggera", command=self.clear_cache + ) + options_menu.add_command( label="Zregeneruj bazę tagów", command=self.regenerate_tags_db ) + menubar.add_cascade(label="Opcje", menu=options_menu) def regenerate_tags_db(self): self.processing_dialog = ProcessingDialog(self, self.tags_repo.regenerate_db) @@ -362,7 +424,7 @@ class ImageBrowser(tk.Tk): main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Lewa kolumna – lista plików left_frame = tk.Frame(main_frame) - left_frame.grid(row=0, column=0, sticky="ns") + left_frame.grid(row=0, column=0, sticky=tk.NS) self.listbox = tk.Listbox(left_frame, width=30) self.listbox.pack(side=tk.LEFT, fill=tk.Y) self.listbox.bind("<>", self.on_listbox_select) @@ -374,7 +436,7 @@ class ImageBrowser(tk.Tk): # Środkowa kolumna – podgląd obrazu center_frame = tk.Frame(main_frame) - center_frame.grid(row=0, column=1, sticky="nsew", padx=10) + center_frame.grid(row=0, column=1, sticky=tk.NSEW, padx=10) main_frame.grid_columnconfigure(1, weight=1) main_frame.grid_rowconfigure(0, weight=1) main_frame.grid_rowconfigure(1, weight=0) @@ -384,7 +446,7 @@ class ImageBrowser(tk.Tk): # Prawa kolumna – panel tagów i uploadu (ograniczona szerokość) right_frame = tk.Frame(main_frame, width=300) - right_frame.grid(row=0, column=2, sticky="nsew", padx=5) + right_frame.grid(row=0, column=2, sticky=tk.NSEW, padx=5) right_frame.grid_propagate(False) right_frame.grid_columnconfigure(0, weight=1) # Ustal wiersze: @@ -396,33 +458,33 @@ class ImageBrowser(tk.Tk): # PNG Tags – widget Text z scrollbar png_frame = tk.LabelFrame(right_frame, text="PNG Tags") - png_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5) + png_frame.grid(row=0, column=0, sticky=tk.EW, padx=5, pady=5) png_frame.grid_columnconfigure(0, weight=1) - self.png_tags_text = tk.Text(png_frame, wrap="word") - self.png_tags_text.grid(row=0, column=0, sticky="ew") + self.png_tags_text = tk.Text(png_frame, wrap=tk.WORD) + self.png_tags_text.grid(row=0, column=0, sticky=tk.EW) scrollbar_png = tk.Scrollbar(png_frame, command=self.png_tags_text.yview) - scrollbar_png.grid(row=0, column=1, sticky="ns") + scrollbar_png.grid(row=0, column=1, sticky=tk.NS) self.png_tags_text.config( - yscrollcommand=scrollbar_png.set, state="disabled", height=4 + yscrollcommand=scrollbar_png.set, state=tk.DISABLED, height=4 ) # Tagger Tags – widget Text z scrollbar tagger_frame = tk.LabelFrame(right_frame, text="Tagger Tags") - tagger_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5) + tagger_frame.grid(row=1, column=0, sticky=tk.EW, padx=5, pady=5) tagger_frame.grid_columnconfigure(0, weight=1) - self.tagger_tags_text = tk.Text(tagger_frame, wrap="word") - self.tagger_tags_text.grid(row=0, column=0, sticky="ew") + self.tagger_tags_text = tk.Text(tagger_frame, wrap=tk.WORD) + self.tagger_tags_text.grid(row=0, column=0, sticky=tk.EW) scrollbar_tagger = tk.Scrollbar( tagger_frame, command=self.tagger_tags_text.yview ) - scrollbar_tagger.grid(row=0, column=1, sticky="ns") + scrollbar_tagger.grid(row=0, column=1, sticky=tk.NS) self.tagger_tags_text.config( - yscrollcommand=scrollbar_tagger.set, state="disabled", height=4 + yscrollcommand=scrollbar_tagger.set, state=tk.DISABLED, height=4 ) # Manual Tags – Entry (stała wysokość) manual_frame = tk.LabelFrame(right_frame, text="Manual Tags") - manual_frame.grid(row=2, column=0, sticky="nsew", padx=5, pady=5) + manual_frame.grid(row=2, column=0, sticky=tk.NSEW, padx=5, pady=5) self.manual_tags_manager = TagManager( manual_frame, self.settings, self.tags_repo ) @@ -436,18 +498,18 @@ class ImageBrowser(tk.Tk): # Final Tags – widget Text z scrollbar, który rozszerza się final_frame = tk.LabelFrame(right_frame, text="Final Tags") - final_frame.grid(row=3, column=0, sticky="nsew", padx=5, pady=5) + final_frame.grid(row=3, column=0, sticky=tk.NSEW, padx=5, pady=5) final_frame.grid_rowconfigure(0, weight=1) final_frame.grid_columnconfigure(0, weight=1) - self.final_tags_text = tk.Text(final_frame, state="disabled", wrap="word") - self.final_tags_text.grid(row=0, column=0, sticky="nsew") + self.final_tags_text = tk.Text(final_frame, state=tk.DISABLED, wrap=tk.WORD) + self.final_tags_text.grid(row=0, column=0, sticky=tk.NSEW) scrollbar_final = tk.Scrollbar(final_frame, command=self.final_tags_text.yview) - scrollbar_final.grid(row=0, column=1, sticky="ns") + scrollbar_final.grid(row=0, column=1, sticky=tk.NS) self.final_tags_text.config(yscrollcommand=scrollbar_final.set) # Panel uploadu i rating – nie zmienia rozmiaru pionowo upload_frame = tk.Frame(right_frame) - upload_frame.grid(row=4, column=0, sticky="ew", padx=5, pady=5) + upload_frame.grid(row=4, column=0, sticky=tk.EW, padx=5, pady=5) self.rating_var = tk.StringVar(value="Unrated") rating_options = ["General", "Sensitive", "Questionable", "Explicit", "Unrated"] self.rating_dropdown = tk.OptionMenu( @@ -458,21 +520,18 @@ class ImageBrowser(tk.Tk): upload_frame, text="Upload", command=self.upload_current_image ) self.upload_button.pack(side=tk.LEFT, padx=5) - self.progress_var = tk.IntVar(value=0) - self.progress_bar = ttk.Progressbar( - upload_frame, - orient="horizontal", - mode="determinate", - variable=self.progress_var, - maximum=100, + self.upload_button.config(state=tk.DISABLED) + self.view_post_button = tk.Button( + upload_frame, text="Wyświetl", command=self.view_current_post ) - self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) + self.view_post_button.pack(side=tk.LEFT, padx=5) + self.view_post_button.config(state=tk.DISABLED) # Na końcu okna głównego dodaj status bar: self.status_label = tk.Label( main_frame, text="", bd=1, relief=tk.SUNKEN, anchor=tk.W ) - self.status_label.grid(row=1, column=0, columnspan=3, sticky="ew") + self.status_label.grid(row=1, column=0, columnspan=3, sticky=tk.EW) def update_status_bar(self): status_text = ( @@ -585,33 +644,77 @@ class ImageBrowser(tk.Tk): ) # Jeśli aktualnie wybrany plik, zmień przycisk if self.current_index == idx: - self.after(0, self.set_upload_button_to_view_or_upload) + self.after(0, self.update_button_states) else: self.uploaded[file_path] = False self.after(0, self.update_status_bar) except Exception as e: print("Błąd podczas sprawdzania paczki uploadu:", e) - def set_upload_button_to_view_or_upload(self): + def update_button_states(self): """ - Ustawia przycisk uploadu na "Wyświetl" lub "Upload" w zależności od stan - uploadu dla aktualnie wybranego pliku. + Update the state of UI elements based on current application state. + + Synchronizes the enabled/disabled states of menu items and buttons with: + - Whether an image is currently selected (current_index exists) + - Whether the selected image has an existing post (post_id exists in uploaded) + + State transitions: + - When no image is selected (current_index is None): + * Disables all upload-related buttons and menu items + - When an image is selected: + * Always enables 'Wyślij wszystko' (Send All) + * Enables 'Wyślij' (Send) if no post exists for the image + * Enables 'Podmień tagi' (Replace Tags) and 'Otwórz post' (Open Post) + if a post exists for the image + * Updates the upload button's text and command based on post existence + + Modifies: + - File menu items: 'Wyślij', 'Wyślij wszystko', 'Podmień tagi', 'Otwórz post' + - Buttons: upload_button, view_post_button + + Dependencies: + - Relies on self.current_index to determine selection state + - Checks self.uploaded against self.image_files for post existence """ + has_current = self.current_index is not None + post_id = ( + self.uploaded.get(self.image_files[self.current_index]) + if has_current + else None + ) + + # Common state for all elements + send_all_state = tk.NORMAL if has_current else tk.DISABLED + post_ops_state = tk.NORMAL if post_id else tk.DISABLED + + # Update menu items + self.file_menu.entryconfig("Wyślij wszystko", state=send_all_state) + self.file_menu.entryconfig("Podmień tagi", state=post_ops_state) + self.file_menu.entryconfig("Otwórz post", state=post_ops_state) + + # Update buttons + self.upload_button.config( + state=tk.NORMAL if has_current else tk.DISABLED, + text="Podmień tagi" if post_id else "Wyślij", + command=self.edit_current_image if post_id else self.upload_current_image, + ) + self.view_post_button.config(state=post_ops_state) + + # Special case for "Wyślij" menu item + wyślij_state = tk.DISABLED if post_id else tk.NORMAL + self.file_menu.entryconfig( + "Wyślij", state=wyślij_state if has_current else tk.DISABLED + ) + + def view_current_post(self): + """Otwiera w przeglądarce URL posta.""" if self.current_index is None: return post_id = self.uploaded.get(self.image_files[self.current_index]) if post_id: - self.upload_button.config( - text="Wyświetl", command=lambda: self.view_post(post_id) - ) - else: - self.upload_button.config(text="Upload", command=self.upload_current_image) - - def view_post(self, post_id): - """Otwiera w przeglądarce URL posta.""" - - url = self.settings.base_url.rstrip("/") + "/post/view/" + str(post_id) - open_webbrowser(url, self.settings) + url = self.settings.base_url.rstrip("/") + "/post/view/" + str(post_id) + open_webbrowser(url, self.settings) def compute_md5(self, file_path, chunk_size=8192): """Oblicza MD5 dla danego pliku.""" @@ -635,7 +738,7 @@ class ImageBrowser(tk.Tk): index = self.listbox.curselection()[0] self.current_index = index self.show_image(index) - self.set_upload_button_to_view_or_upload() + self.update_button_states() def upload_current_image(self): """ @@ -646,13 +749,10 @@ class ImageBrowser(tk.Tk): file_path = self.image_files[self.current_index] if self.uploaded.get(file_path, False): # Jeśli plik już uploadowany, ustaw przycisk na "Wyświetl" - self.set_upload_button_to_view_or_upload() + self.update_button_states() return - self.upload_button.config(state="disabled") - self.progress_var.set(0) - threading.Thread( - target=self.upload_file, args=(file_path,), daemon=True - ).start() + self.upload_button.config(state=tk.DISABLED) + self.processing_dialog = ProcessingDialog(self, self.upload_file, file_path) def show_image(self, index): """ @@ -678,6 +778,18 @@ class ImageBrowser(tk.Tk): except Exception as e: messagebox.showerror("Błąd", f"Nie można załadować obrazka:\n{e}") + def edit_current_image(self): + """ + Modyfikuje obrazek na serwerze. + Wysyła POST z md5 obrazka, tagami i ratingiem. + """ + file_path = self.image_files[self.current_index] + if not self.uploaded.get(file_path, False): + self.update_button_states() + return + self.upload_button.config(state=tk.DISABLED) + self.processing_dialog = ProcessingDialog(self, self.edit_file, file_path) + def update_display_image(self): """ Aktualizuje obrazek na podstawie aktualnego rozmiaru okna. @@ -756,7 +868,7 @@ class ImageBrowser(tk.Tk): self.listbox.select_set(new_index) self.listbox.activate(new_index) self.show_image(new_index) - self.set_upload_button_to_view_or_upload() + self.update_button_states() def show_next(self): """ @@ -772,7 +884,7 @@ class ImageBrowser(tk.Tk): self.listbox.select_set(new_index) self.listbox.activate(new_index) self.show_image(new_index) - self.set_upload_button_to_view_or_upload() + self.update_button_states() # --- Metody obsługujące widgety z tagami --- @@ -781,7 +893,7 @@ class ImageBrowser(tk.Tk): Aktualizuje widget z tagami z PNG. Tworzy tagi jako klikalne, zaznaczone lub niezaznaczone. """ - self.png_tags_text.config(state="normal") + self.png_tags_text.config(state=tk.NORMAL) self.png_tags_text.delete("1.0", tk.END) self.png_tags_states = {} for tag in tags_list: @@ -794,7 +906,7 @@ class ImageBrowser(tk.Tk): self.png_tags_text.tag_configure(tag_name, foreground="blue") self.png_tags_text.tag_bind(tag_name, "", self.toggle_png_tag) self.png_tags_text.insert(tk.INSERT, " ") - self.png_tags_text.config(state="disabled") + self.png_tags_text.config(state=tk.DISABLED) self.adjust_text_widget_height(self.png_tags_text) self.update_final_tags() @@ -933,9 +1045,9 @@ class ImageBrowser(tk.Tk): self.png_tags_states[tag] = tag in common tag_name = "png_" + tag color = "blue" if self.png_tags_states[tag] else "black" - self.png_tags_text.config(state="normal") + self.png_tags_text.config(state=tk.NORMAL) self.png_tags_text.tag_configure(tag_name, foreground=color) - self.png_tags_text.config(state="disabled") + self.png_tags_text.config(state=tk.DISABLED) for tag in self.tagger_tags_states: if tag.startswith("character:"): @@ -956,7 +1068,7 @@ class ImageBrowser(tk.Tk): Poprawia wysokość widgetu. Aktualizuje listę finalnych tagów. """ - self.tagger_tags_text.config(state="normal") + self.tagger_tags_text.config(state=tk.NORMAL) self.tagger_tags_text.delete("1.0", tk.END) visible_tags = self.get_visible_tags() @@ -974,7 +1086,7 @@ class ImageBrowser(tk.Tk): ) self.tagger_tags_text.insert(tk.INSERT, " ") - self.tagger_tags_text.config(state="disabled") + self.tagger_tags_text.config(state=tk.DISABLED) self.adjust_text_widget_height(self.tagger_tags_text) self.update_final_tags() @@ -1035,7 +1147,7 @@ class ImageBrowser(tk.Tk): final_set.update(manual) final_list = sorted(final_set) - self.final_tags_text.config(state="normal") + self.final_tags_text.config(state=tk.NORMAL) self.final_tags_text.delete("1.0", tk.END) for tag in final_list: _, deprecated = process_tag(tag, self.tags_repo) @@ -1061,7 +1173,7 @@ class ImageBrowser(tk.Tk): tag_name, "", self.open_final_tag_wiki_url ) self.final_tags_text.insert(tk.INSERT, " ") - self.final_tags_text.config(state="disabled") + self.final_tags_text.config(state=tk.DISABLED) def open_final_tag_wiki_url(self, event): """Otwiera w przeglądarce URL strony wiki dla klikniętego tagu. @@ -1101,10 +1213,10 @@ class ImageBrowser(tk.Tk): if self.current_index is None: return # Ustaw komunikat, że Tagger pracuje - self.tagger_tags_text.config(state="normal") + self.tagger_tags_text.config(state=tk.NORMAL) self.tagger_tags_text.delete("1.0", tk.END) self.tagger_tags_text.insert("1.0", "Tagger przetwarza...") - self.tagger_tags_text.config(state="disabled") + self.tagger_tags_text.config(state=tk.DISABLED) file_path = self.image_files[self.current_index] result = self.get_tagger_results(file_path) new_rating = self.map_tagger_rating(result) @@ -1112,7 +1224,14 @@ class ImageBrowser(tk.Tk): self.after(0, lambda: self.update_tagger_tags_widget(result)) self.update_listbox_item_color_by_rating(file_path, result.rating) - def upload_file(self, file_path, final_tags=None, final_rating=None): + def upload_file( + self, file_path, final_tags=None, final_rating=None, progress_queue=None + ): + base_file_name = os.path.basename(file_path) + if progress_queue: + progress_queue.put(("mode", "determinate")) + progress_queue.put(("max", 100)) + progress_queue.put(("label", f"Wysyłam plik {base_file_name}...")) url = self.settings.base_url.rstrip("/") + "/api/danbooru/add_post" tags = ( self.final_tags_text.get("1.0", tk.END).strip() @@ -1134,16 +1253,16 @@ class ImageBrowser(tk.Tk): total_size = os.path.getsize(file_path) def progress_callback(bytes_read, total_size): - percentage = int(bytes_read / total_size * 100) - self.progress_bar.after(0, lambda: self.progress_var.set(percentage)) + if progress_queue: + percentage = int(bytes_read / total_size * 100) + progress_queue.put(("progress", percentage)) with open(file_path, "rb") as f: wrapped_file = ProgressFile(f, progress_callback, total_size) - files = { - "file": (os.path.basename(file_path), wrapped_file, "image/png") - } + files = {"file": (base_file_name, wrapped_file, "image/png")} response = requests.post(url, data=fields, files=files) - self.progress_bar.after(0, lambda: self.progress_var.set(100)) + if progress_queue: + progress_queue.put(("progress", 100)) show_warn = False post_url = None if response.status_code in (200, 201): @@ -1156,9 +1275,6 @@ class ImageBrowser(tk.Tk): else: message = f"Upload zakończony błędem.\nStatus: {response.status_code}\nTreść: {response.text}" show_warn = True - self.upload_button.after( - 0, lambda: self.upload_button.config(state="normal") - ) # Aktualizacja wyglądu listy – musimy użyć domyślnych argumentów w lambdzie, aby zachować bieżący indeks if show_warn: if not final_tags: @@ -1177,13 +1293,101 @@ class ImageBrowser(tk.Tk): self.uploaded[file_path] = post_id self.uploaded_count += 1 self.after(0, self.update_status_bar) - self.set_upload_button_to_view_or_upload() + self.after(0, self.update_button_states) except Exception as e: - self.upload_button.after( - 0, lambda: self.upload_button.config(state="normal") - ) + self.upload_button.after(0, self.update_button_states) messagebox.showerror("Błąd uploadu", str(e)) + def edit_file( + self, file_path, final_tags=None, final_rating=None, progress_queue=None + ): + """ + Update tags and rating for an existing post without uploading the file. + + Args: + file_path: Path to the image file associated with the post + final_tags: Optional override tags (space-separated string) + final_rating: Optional override rating + progress_queue: Progress communication queue + + Requires: + - The file must have an existing post ID in self.uploaded + """ + base_file_name = os.path.basename(file_path) + post_id = self.uploaded.get(file_path) + + if not post_id: + messagebox.showerror( + "Błąd edycji", "Post nie został znaleziony dla tego pliku" + ) + return + + if progress_queue: + progress_queue.put(("mode", "determinate")) + progress_queue.put(("max", 100)) + progress_queue.put(("label", f"Aktualizuję tagi dla {base_file_name}...")) + + try: + # Get authentication session and token + session = login(self.settings) + auth_token = get_auth_token(session, self.settings) + + # Prepare tags and rating + tags = ( + self.final_tags_text.get("1.0", tk.END).strip() + if final_tags is None + else final_tags + ) + + rating_value = self.rating_map.get( + self.rating_var.get() if final_rating is None else final_rating, "?" + ) + + # Prepare API request + url = self.settings.base_url.rstrip("/") + "/post/set" + payload = { + "auth_token": auth_token, + "image_id": post_id, + "title": base_file_name, + "owner": self.settings.username, + "tags": tags, + "source": "", + "rating": rating_value, + } + + if progress_queue: + progress_queue.put(("progress", 50)) + + # Send update request + response = session.post(url, data=payload, allow_redirects=False) + + # Handle 302 redirect as success case + if response.status_code == 302: + if progress_queue: + progress_queue.put(("progress", 100)) + + message = "Tagi zostały zaktualizowane!" + if not final_tags: # Only show success if not bulk operation + messagebox.showinfo("Sukces edycji", message) + + # Update UI state + self.after(0, self.update_button_states) + return + + # Handle other status codes + error_msg = ( + f"Błąd podczas aktualizacji tagów\nStatus: {response.status_code}" + ) + if response.text: + error_msg += f"\nTreść: {response.text}" + messagebox.showerror("Błąd edycji", error_msg) + + except Exception as e: + messagebox.showerror("Krytyczny błąd edycji", str(e)) + finally: + if progress_queue: + progress_queue.put(("progress", 100)) + def upload_all_files(self): """ Metoda, która po potwierdzeniu przez użytkownika uploaduje wszystkie niewrzucone pliki. diff --git a/kapitanbooru_uploader/autocomplete.py b/kapitanbooru_uploader/autocomplete.py index 27d1281..b03ca69 100644 --- a/kapitanbooru_uploader/autocomplete.py +++ b/kapitanbooru_uploader/autocomplete.py @@ -179,20 +179,24 @@ class TagManager(tk.Frame): based on custom logic. """ - def __init__(self, master, settings: Settings, tags_repo: TagsRepo, *args, **kwargs): + def __init__( + self, master, settings: Settings, tags_repo: TagsRepo, *args, **kwargs + ): super().__init__(master, *args, **kwargs) self.tags_repo = tags_repo self.settings = settings self.manual_tags = [] # List to hold manually entered tags # Entry for new tags (with autocompletion) - self.entry = AutocompleteEntry(self, callback=self.add_tag, tags_repo=self.tags_repo) + self.entry = AutocompleteEntry( + self, callback=self.add_tag, tags_repo=self.tags_repo + ) self.entry.pack(fill=tk.X, padx=5, pady=5) # Text widget for displaying already entered tags - self.tags_display = tk.Text(self, wrap="word", height=4) + self.tags_display = tk.Text(self, wrap=tk.WORD, height=4) self.tags_display.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) - self.tags_display.config(state="disabled") + self.tags_display.config(state=tk.DISABLED) # (Optional: add a scrollbar if needed) def add_tag(self, tag): @@ -229,7 +233,7 @@ class TagManager(tk.Frame): self.tags_display.tag_bind(tag_name, "", self.remove_tag) self.tags_display.tag_bind(tag_name, "", self.open_tag_wiki_url) self.tags_display.insert(tk.INSERT, " ") - self.tags_display.config(state="disabled") + self.tags_display.config(state=tk.DISABLED) def remove_tag(self, event): """Remove the clicked tag from the list and update the display.""" diff --git a/pyproject.toml b/pyproject.toml index cd906ec..82e2e53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "kapitanbooru-uploader" -version = "0.1.0" +version = "0.2.0" description = "A GUI application for uploading images to KapitanBooru" authors = [ {name = "Michał Leśniak", email = "kapitan@mlesniak.pl"}