407 lines
16 KiB
Python
407 lines
16 KiB
Python
"""
|
||
Интерфейс (UI) для приложения автоматизации загрузки файлов
|
||
"""
|
||
import tkinter as tk
|
||
from tkinter import filedialog, messagebox, ttk
|
||
import threading
|
||
import logging
|
||
from pathlib import Path
|
||
from services.processor import FileProcessorService
|
||
from services.browser_service import BrowserService
|
||
|
||
|
||
class UIManager:
|
||
"""Менеджер интерфейса приложения"""
|
||
|
||
def __init__(self, root):
|
||
"""Инициализирует интерфейс."""
|
||
self.root = root
|
||
self.root.title("🔄 Автоматизация загрузки файлов")
|
||
self.root.geometry("700x800")
|
||
self.root.configure(bg="#2C3E50")
|
||
self.root.minsize(500, 400)
|
||
|
||
self.font_title = ("Arial", 14, "bold")
|
||
self.font_label = ("Arial", 11)
|
||
self.font_button = ("Arial", 10, "bold")
|
||
|
||
self.processor = None
|
||
self.processing = False
|
||
|
||
self._setup_logging()
|
||
self._create_ui()
|
||
|
||
def _setup_logging(self):
|
||
"""Настраивает логирование в текстовое поле."""
|
||
# Обычное логирование в консоль
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||
)
|
||
|
||
def _create_ui(self):
|
||
"""Создает элементы интерфейса."""
|
||
# Главный фрейм
|
||
main_frame = tk.Frame(self.root, bg="#2C3E50")
|
||
main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
|
||
|
||
# Заголовок
|
||
title_label = tk.Label(
|
||
main_frame,
|
||
text="📁 Автоматизация загрузки файлов XLSX",
|
||
font=self.font_title,
|
||
bg="#2C3E50",
|
||
fg="#ECF0F1"
|
||
)
|
||
title_label.pack(pady=(0, 20))
|
||
|
||
# ===== ПОЛЕ 1: ДИРЕКТОРИЯ С ФАЙЛАМИ =====
|
||
dir_label = tk.Label(
|
||
main_frame,
|
||
text="📂 Директория с файлами .xlsx:",
|
||
font=self.font_label,
|
||
bg="#2C3E50",
|
||
fg="#ECF0F1"
|
||
)
|
||
dir_label.pack(anchor="w", pady=(10, 5))
|
||
|
||
dir_frame = tk.Frame(main_frame, bg="#2C3E50")
|
||
dir_frame.pack(fill=tk.X, pady=(0, 15))
|
||
|
||
self.dir_entry = tk.Entry(dir_frame, font=self.font_label, width=60)
|
||
self.dir_entry.pack(side="left", fill=tk.X, expand=True)
|
||
self.dir_entry.bind("<Button-1>", lambda e: self._select_directory())
|
||
|
||
dir_button = tk.Button(
|
||
dir_frame,
|
||
text="🗂️ Выбрать",
|
||
command=self._select_directory,
|
||
font=self.font_button,
|
||
bg="#3498DB",
|
||
fg="white",
|
||
padx=10
|
||
)
|
||
dir_button.pack(side="left", padx=(10, 0))
|
||
|
||
# ===== ПОЛЕ 2: URL СТРАНИЦЫ =====
|
||
url_label = tk.Label(
|
||
main_frame,
|
||
text="🌐 URL страницы для загрузки:",
|
||
font=self.font_label,
|
||
bg="#2C3E50",
|
||
fg="#ECF0F1"
|
||
)
|
||
url_label.pack(anchor="w", pady=(10, 5))
|
||
|
||
# Кнопка Вставить рядом с полем URL
|
||
url_frame = tk.Frame(main_frame, bg="#2C3E50")
|
||
url_frame.pack(fill=tk.X, pady=(0, 15))
|
||
|
||
self.url_entry = tk.Entry(url_frame, font=self.font_label)
|
||
self.url_entry.pack(side="left", fill=tk.X, expand=True)
|
||
|
||
insert_button = tk.Button(
|
||
url_frame,
|
||
text="Вставить",
|
||
command=self._insert_from_clipboard,
|
||
font=self.font_button,
|
||
bg="#27AE60",
|
||
fg="white",
|
||
padx=10
|
||
)
|
||
insert_button.pack(side="left", padx=(10, 0))
|
||
|
||
# ===== ПОЛЕ 3: ВАРИАНТЫ КНОПОК =====
|
||
buttons_label = tk.Label(
|
||
main_frame,
|
||
text="🔘 Варианты текста кнопок (через запятую):",
|
||
font=self.font_label,
|
||
bg="#2C3E50",
|
||
fg="#ECF0F1"
|
||
)
|
||
buttons_label.pack(anchor="w", pady=(10, 5))
|
||
|
||
self.upload_buttons_entry = tk.Entry(main_frame, font=self.font_label)
|
||
self.upload_buttons_entry.pack(anchor="w", fill=tk.X, pady=(0, 5))
|
||
self.upload_buttons_entry.insert(0, "Загрузить файл, Выбрать файл, Добавить файл")
|
||
|
||
upload_label = tk.Label(
|
||
main_frame,
|
||
text="(Кнопка для загрузки файла)",
|
||
font=("Arial", 9),
|
||
bg="#2C3E50",
|
||
fg="#BDC3C7"
|
||
)
|
||
upload_label.pack(anchor="w", pady=(0, 10))
|
||
|
||
self.process_buttons_entry = tk.Entry(main_frame, font=self.font_label)
|
||
self.process_buttons_entry.pack(anchor="w", fill=tk.X, pady=(0, 5))
|
||
self.process_buttons_entry.insert(0, "Загрузить, Обработать, Преобразовать, Изменить, Конвертировать")
|
||
|
||
process_label = tk.Label(
|
||
main_frame,
|
||
text="(Кнопка для обработки/загрузки файла)",
|
||
font=("Arial", 9),
|
||
bg="#2C3E50",
|
||
fg="#BDC3C7"
|
||
)
|
||
process_label.pack(anchor="w", pady=(0, 15))
|
||
|
||
# ===== ЛОГИРОВАНИЕ =====
|
||
log_label = tk.Label(
|
||
main_frame,
|
||
text="📋 Логирование процесса:",
|
||
font=self.font_label,
|
||
bg="#2C3E50",
|
||
fg="#ECF0F1"
|
||
)
|
||
log_label.pack(anchor="w", pady=(10, 5))
|
||
|
||
# Frame для логов с прокруткой
|
||
log_frame = tk.Frame(main_frame, bg="#34495E")
|
||
log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
|
||
|
||
scrollbar = tk.Scrollbar(log_frame)
|
||
scrollbar.pack(side="right", fill="y")
|
||
|
||
self.log_text = tk.Text(
|
||
log_frame,
|
||
font=("Courier", 9),
|
||
bg="#1C2833",
|
||
fg="#82E0AA",
|
||
height=10,
|
||
yscrollcommand=scrollbar.set
|
||
)
|
||
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||
scrollbar.config(command=self.log_text.yview)
|
||
|
||
# ===== КНОПКИ =====
|
||
button_frame = tk.Frame(main_frame, bg="#2C3E50")
|
||
button_frame.pack(fill=tk.X)
|
||
|
||
self.start_button = tk.Button(
|
||
button_frame,
|
||
text="🚀 Начать загрузку",
|
||
command=self._start_processing,
|
||
font=self.font_button,
|
||
bg="#27AE60",
|
||
fg="white",
|
||
padx=20,
|
||
pady=12
|
||
)
|
||
self.start_button.pack(side="right", padx=(10, 0))
|
||
|
||
self.clear_log_button = tk.Button(
|
||
button_frame,
|
||
text="🗑️ Очистить логи",
|
||
command=self._clear_logs,
|
||
font=self.font_button,
|
||
bg="#E74C3C",
|
||
fg="white",
|
||
padx=15,
|
||
pady=12
|
||
)
|
||
self.clear_log_button.pack(side="right")
|
||
|
||
# Перенаправление логов в текстовое поле
|
||
self._setup_log_handler()
|
||
|
||
# ===== ВЫБОР ВКЛАДКИ =====
|
||
tabs_label = tk.Label(
|
||
main_frame,
|
||
text="📑 Выбор вкладки браузера:",
|
||
font=self.font_label,
|
||
bg="#2C3E50",
|
||
fg="#ECF0F1"
|
||
)
|
||
tabs_label.pack(anchor="w", pady=(10, 5))
|
||
|
||
self.tab_selector = TabSelectorApp(main_frame, BrowserService(download_dir="./downloads"))
|
||
self.tab_selector.pack(fill=tk.X, pady=(0, 15))
|
||
|
||
def _setup_log_handler(self):
|
||
"""Настраивает обработчик логов в текстовое поле."""
|
||
# Создаем кастомный обработчик для логирования в UI
|
||
class TextHandler(logging.Handler):
|
||
def __init__(self, text_widget):
|
||
super().__init__()
|
||
self.text_widget = text_widget
|
||
|
||
def emit(self, record):
|
||
msg = self.format(record)
|
||
self.text_widget.insert(tk.END, msg + "\n")
|
||
self.text_widget.see(tk.END)
|
||
self.text_widget.update()
|
||
|
||
handler = TextHandler(self.log_text)
|
||
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%H:%M:%S'))
|
||
logging.getLogger().addHandler(handler)
|
||
|
||
def _select_directory(self):
|
||
"""Открывает диалог выбора директории."""
|
||
directory = filedialog.askdirectory(title="Выберите директорию с .xlsx файлами")
|
||
if directory:
|
||
self.dir_entry.delete(0, tk.END)
|
||
self.dir_entry.insert(0, directory)
|
||
logging.info(f"✅ Выбрана директория: {directory}")
|
||
|
||
def _clear_logs(self):
|
||
"""Очищает логи."""
|
||
self.log_text.delete(1.0, tk.END)
|
||
logging.info("🗑️ Логи очищены")
|
||
|
||
def _start_processing(self):
|
||
"""Запускает процесс обработки в отдельном потоке."""
|
||
if self.processing:
|
||
messagebox.showwarning("⚠️ Внимание", "Процесс уже запущен!")
|
||
return
|
||
|
||
# Валидация входных данных
|
||
directory = self.dir_entry.get().strip()
|
||
url = self.url_entry.get().strip()
|
||
|
||
if not directory:
|
||
messagebox.showerror("❌ Ошибка", "Выберите директорию с файлами!")
|
||
return
|
||
|
||
if not Path(directory).exists():
|
||
messagebox.showerror("❌ Ошибка", "Указанная директория не существует!")
|
||
return
|
||
|
||
if not url or url == "https://":
|
||
messagebox.showerror("❌ Ошибка", "Введите URL страницы!")
|
||
return
|
||
|
||
# Парсим варианты кнопок
|
||
upload_texts = [t.strip() for t in self.upload_buttons_entry.get().split(",")]
|
||
process_texts = [t.strip() for t in self.process_buttons_entry.get().split(",")]
|
||
|
||
# Отключаем кнопку и запускаем обработку в потоке
|
||
self.processing = True
|
||
self.start_button.config(state="disabled", bg="#95A5A6")
|
||
|
||
thread = threading.Thread(
|
||
target=self._processing_thread,
|
||
args=(directory, url, upload_texts, process_texts),
|
||
daemon=True
|
||
)
|
||
thread.start()
|
||
|
||
def _processing_thread(self, directory, url, upload_texts, process_texts):
|
||
"""Потоковая функция обработки."""
|
||
try:
|
||
logging.info("\n" + "="*70)
|
||
logging.info("🔄 ЗАПУСК АВТОМАТИЗАЦИИ ЗАГРУЗКИ ФАЙЛОВ")
|
||
logging.info("="*70)
|
||
|
||
# Получаем путь для загрузок (рядом с программой)
|
||
download_dir = str(Path(__file__).parent / "downloads")
|
||
Path(download_dir).mkdir(exist_ok=True)
|
||
|
||
# Создаем процессор
|
||
processor = FileProcessorService(directory, url, download_dir)
|
||
|
||
# Проверка вкладок перед началом загрузки
|
||
browser_service = BrowserService(download_dir="./downloads")
|
||
browser_service.set_tabs(["Вкладка 1", "Вкладка 2", "Вкладка 3"])
|
||
|
||
# Пример: выбор вкладки по индексу
|
||
if not browser_service.select_tab_from_list(0):
|
||
logging.error("❌ Не удалось выбрать вкладку. Проверьте массив вкладок.")
|
||
return
|
||
|
||
# Запускаем обработку
|
||
results = processor.start_processing(upload_texts, process_texts)
|
||
|
||
# Показываем результаты
|
||
self._show_results(results)
|
||
|
||
except Exception as e:
|
||
logging.error(f"❌ Критическая ошибка: {e}")
|
||
messagebox.showerror("❌ Ошибка", f"Произошла ошибка:\n{str(e)}")
|
||
finally:
|
||
self.processing = False
|
||
self.start_button.config(state="normal", bg="#27AE60")
|
||
|
||
def _show_results(self, results):
|
||
"""Показывает результаты обработки."""
|
||
result_text = (
|
||
f"✅ ОБРАБОТКА ЗАВЕРШЕНА\n\n"
|
||
f"📊 Результаты:\n"
|
||
f"• Всего файлов найдено: {results['total_files']}\n"
|
||
f"• Обработано успешно: {results['processed_files']}\n"
|
||
f"• Ошибок: {len(results['failed_files'])}\n"
|
||
)
|
||
|
||
if results['failed_files']:
|
||
result_text += f"\n❌ Файлы с ошибками:\n"
|
||
for file in results['failed_files'][:5]:
|
||
result_text += f" • {file}\n"
|
||
if len(results['failed_files']) > 5:
|
||
result_text += f" ... и еще {len(results['failed_files']) - 5}"
|
||
|
||
if results['errors']:
|
||
result_text += f"\n⚠️ Системные ошибки:\n"
|
||
for error in results['errors']:
|
||
result_text += f" • {error}\n"
|
||
|
||
messagebox.showinfo("Результаты", result_text)
|
||
|
||
def _insert_from_clipboard(self):
|
||
"""Обработчик для вставки текста из буфера обмена."""
|
||
try:
|
||
clipboard_text = self.root.clipboard_get()
|
||
self.url_entry.delete(0, tk.END) # Очистить поле
|
||
self.url_entry.insert(0, clipboard_text) # Вставить текст из буфера
|
||
except tk.TclError:
|
||
messagebox.showerror("Ошибка", "Буфер обмена пуст или недоступен.")
|
||
|
||
|
||
class TabSelectorApp:
|
||
def __init__(self, parent, browser_service):
|
||
self.browser_service = browser_service
|
||
self.frame = tk.Frame(parent, bg="#2C3E50")
|
||
|
||
# Выпадающий список
|
||
self.combo = ttk.Combobox(self.frame, state="readonly")
|
||
self.combo.pack(pady=10)
|
||
|
||
# Кнопка для выбора вкладки
|
||
self.select_button = tk.Button(self.frame, text="Выбрать вкладку", command=self.select_tab)
|
||
self.select_button.pack(pady=10)
|
||
|
||
def pack(self, **kwargs):
|
||
"""Упрощает упаковку фрейма."""
|
||
self.frame.pack(**kwargs)
|
||
|
||
def set_tabs(self, tabs):
|
||
"""Устанавливает массив вкладок и обновляет выпадающий список."""
|
||
self.tabs = tabs
|
||
self.combo["values"] = self.tabs
|
||
if self.tabs:
|
||
self.combo.current(0) # Устанавливаем первый элемент по умолчанию
|
||
|
||
def select_tab(self):
|
||
"""Выбирает вкладку из выпадающего списка."""
|
||
selected_index = self.combo.current()
|
||
if selected_index == -1:
|
||
print("❌ Вкладка не выбрана.")
|
||
return
|
||
|
||
success = self.browser_service.select_tab_from_list(selected_index)
|
||
if success:
|
||
print(f"✅ Вкладка '{self.tabs[selected_index]}' выбрана.")
|
||
else:
|
||
print(f"❌ Не удалось выбрать вкладку '{self.tabs[selected_index]}'." )
|
||
|
||
|
||
def main():
|
||
"""Главная функция."""
|
||
root = tk.Tk()
|
||
app = UIManager(root)
|
||
root.mainloop()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|