AutoPercenty3/gui.py

617 lines
27 KiB
Python

from PySide6.QtWidgets import QWidget, QPushButton, QVBoxLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
from PySide6.QtCore import Qt, QRect, QSettings, QTimer
from toggleSwitch import ToggleSwitch
from browser_control import BrowserController
from whale_translator import WhaleTranslator
from clipboardImageManager import ClipboardImageManager
from vertexAI import VertexAITranslator
from option import OptionHandler
from price import PriceHandler
from locatorManager import LocatorManager
from logger_module import QTextEditLogger # 추가
import logging
import asyncio
class TranslationApp(QWidget):
def __init__(self, logger=None, whale_translator=None):
super().__init__()
self.initUI()
self.logger = logger
self.debug = False
key_path = 'leensoo1nt.json'
self.settings = QSettings("WhenRideMycar", "TranslationApp") # QSettings 초기화
self.locator_manager = LocatorManager()
self.browser_controller = BrowserController(self, self.logger, self.locator_manager)
# self.whale_translator = WhaleTranslator(self, self.logger, secret_mode=True,vd_mode=True) # 디버그 모드 켜기
self.whale_translator = whale_translator
self.vertexAI = VertexAITranslator(self.logger, key_path)
self.optionHandler = None
self.clipboardImageManager = ClipboardImageManager(self, logger, self.browser_controller, self.debug)
self.optionHandler = OptionHandler(self.locator_manager, self.browser_controller, self.whale_translator, self.logger, self.vertexAI, self.debug)
self.priceHandler = PriceHandler(self.locator_manager, self.browser_controller, self.logger, self.vertexAI, self.debug)
self.running = False
# 변수 설정
self.start_time = 0
self.finish_time = 0
self.total_product_count = 0
self.current_product_count = 0
self.title_count = 0
self.option_count = 0
self.price_count = 0
self.detail_image_count = 0
self.thumb_image_count = 0
self.current_stage_index = 0 # 현재 진행 중인 단계 인덱스
# 이전에 저장된 설정 불러오기
self.load_settings()
# 로거 초기화
self.add_text_edit_logger()
# 프로그래스바 초기화
self.update_total_progress(0,0)
async def run_async_tasks(self):
"""비동기 작업을 실행"""
while True:
await asyncio.sleep(0.1) # 비동기적으로 잠시 대기하여 응답성을 유지
def add_text_edit_logger(self):
"""QTextEdit에 로그를 출력하기 위한 핸들러 추가"""
text_edit_logger = QTextEditLogger()
text_edit_logger.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
# text_edit_logger.appendHtml.connect(self.log.appendHtml)
text_edit_logger.appendHtml.connect(self.log.append) # appendHtml 대신 append로 수정
text_edit_logger.scrollToBottom.connect(lambda: self.log.verticalScrollBar().setValue(self.log.verticalScrollBar().maximum()))
self.logger.addHandler(text_edit_logger)
self.logger.debug('로그기록이 설정되었습니다.')
def start_stage(self, stage_index):
"""지정한 단계에 깜빡임 효과 적용"""
if 0 <= stage_index < len(self.stage_labels):
self.timer = QTimer(self)
self.blink_status = True
self.timer.timeout.connect(lambda: self.blink_stage(stage_index))
self.timer.start(500) # 0.5초 간격으로 깜빡임
def blink_stage(self, stage_index):
"""지정한 단계의 색상을 주기적으로 변경하여 깜빡임 효과를 적용"""
label = self.stage_labels[stage_index]
if self.blink_status:
label.setStyleSheet("background-color: yellow; padding: 5px;")
else:
label.setStyleSheet("background-color: lightgray; padding: 5px;")
self.blink_status = not self.blink_status
def stop_blinking_effect(self):
"""깜빡임 효과 중지"""
self.timer.stop()
def complete_stage(self, stage_index):
"""단계 완료 시 깜빡임을 중지하고 완료 상태로 변경"""
if 0 <= stage_index < len(self.stage_labels):
self.stop_blinking_effect()
label = self.stage_labels[stage_index]
label.setStyleSheet("background-color: green; padding: 5px;")
self.current_stage_index += 1
# 다음 단계로 이동하여 깜빡임 시작
if self.current_stage_index < len(self.stages):
self.start_stage(self.current_stage_index)
def initUI(self):
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.setGeometry(QRect(1240, 750, 280, 600))
self.setWindowTitle('이미지 번역 도구')
# 로그
self.log = QTextEdit(self)
self.log.setReadOnly(True)
# 전체 프로그레스바
self.total_progress_bar = QProgressBar(self)
self.total_progress_bar.setValue(0)
self.total_progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
# 스테이지 타임라인
self.stageTimeline_layout = QHBoxLayout()
# self.stages = ["상품명", "옵션", "가격", "썸네일", "상페"]
self.stages = ["옵션", "상페"]
self.stage_labels = []
for stage in self.stages:
self.stage_layout = QHBoxLayout()
label = QLabel(stage)
label.setStyleSheet("background-color: lightgray; padding: 5px;")
self.stage_labels.append(label)
self.stage_layout.addWidget(label)
self.stageTimeline_layout.addLayout(self.stage_layout)
# 디테일 프로그레스바
self.detail_progress_bar = QProgressBar(self)
self.detail_progress_bar.setValue(0)
self.detail_progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.detail_progress_bar.setVisible(False)
# 관리자 토글
self.admin_toggle = ToggleSwitch(self)
self.admin_toggle.clicked.connect(self.on_toggle_clicked)
# 관리자 ID 및 PW
self.admin_id_label = QLabel("관리자 ID:", self)
self.admin_id_input = QLineEdit(self)
# 관리자 PW
self.admin_pw_label = QLabel("관리자 PW:", self)
self.admin_pw_input = QLineEdit(self)
self.admin_pw_input.setEchoMode(QLineEdit.Password)
# 직원 ID 및 PW
self.user_id_label = QLabel("직원 ID:", self)
self.user_id_input = QLineEdit(self)
self.user_pw_label = QLabel("직원 PW:", self)
self.user_pw_input = QLineEdit(self)
self.user_pw_input.setEchoMode(QLineEdit.Password)
# 크롬 실행 버튼 및 번역 버튼
self.start_chrome_button = QPushButton('크롬 실행', self)
self.translate_button = QPushButton('번역 시작', self)
self.pause_button = QPushButton('일시정지', self)
self.exit_button = QPushButton('종료', self)
# 버튼 크기를 1.5배로 설정
button_height = int(self.start_chrome_button.sizeHint().height() * 1.5)
self.start_chrome_button.setFixedHeight(button_height)
self.translate_button.setFixedHeight(button_height)
self.pause_button.setFixedHeight(button_height)
self.exit_button.setFixedHeight(button_height)
# 메인 레이아웃 설정
self.main_layout = QVBoxLayout()
# 관리자 토글 버튼 및 로그인 관련 필드 추가
self.toggle_layout = QHBoxLayout()
self.toggle_layout.addWidget(QLabel("관리자 여부:", self))
self.toggle_layout.addWidget(self.admin_toggle)
self.main_layout.addLayout(self.toggle_layout,1)
# 관리자 ID
self.main_layout.addWidget(self.admin_id_label)
self.main_layout.addWidget(self.admin_id_input)
# 관리자 PW
self.admin_layout = QVBoxLayout()
self.admin_layout.addWidget(self.admin_pw_label)
self.admin_layout.addWidget(self.admin_pw_input)
# 직원 ID/PW
self.user_layout = QVBoxLayout()
self.user_layout.addWidget(self.user_id_label)
self.user_layout.addWidget(self.user_id_input)
self.user_layout.addWidget(self.user_pw_label)
self.user_layout.addWidget(self.user_pw_input)
# 관리자와 직원 레이아웃을 메인 레이아웃에 추가
self.main_layout.addLayout(self.admin_layout,3)
self.main_layout.addLayout(self.user_layout,3)
# 크롬 및 번역 관련 버튼
self.button_layout = QHBoxLayout()
self.button_layout.addWidget(self.start_chrome_button)
self.button_layout.addWidget(self.translate_button)
self.button_layout.addWidget(self.pause_button)
self.button_layout.addWidget(self.exit_button)
# 로그 및 프로그레스바 레이아웃
self.log_layout = QVBoxLayout()
self.log_layout.addWidget(self.log)
self.log_layout.addWidget(self.total_progress_bar)
self.log_layout.addLayout(self.stageTimeline_layout)
self.log_layout.addWidget(self.detail_progress_bar)
# 메인 레이아웃에 버튼 레이아웃과 로그 레이아웃 추가
self.main_layout.addLayout(self.button_layout,2)
self.main_layout.addLayout(self.log_layout,5)
self.setLayout(self.main_layout)
# 기본 상태 설정
self.on_toggle_clicked(False)
# 버튼 이벤트 연결
# self.start_chrome_button.clicked.connect(self.start_browser)
self.start_chrome_button.clicked.connect(self.on_start_chrome_button_clicked)
# self.translate_button.clicked.connect(self.start_translation)
self.translate_button.clicked.connect(self.on_start_translation_button_clicked)
self.pause_button.clicked.connect(self.pause_translation)
self.exit_button.clicked.connect(self.close)
self.exit_button.clicked.connect(self.on_close_button_clicked)
async def run_async_tasks(self):
"""비동기 작업을 실행"""
while True:
await asyncio.sleep(0.1) # 비동기적으로 잠시 대기하여 응답성을 유지
def on_toggle_clicked(self, is_checked):
"""관리자 토글 상태에 따라 관리자와 직원 필드를 표시/숨김"""
if is_checked:
# 관리자 모드: 직원 레이아웃을 숨기고, 관리자 PW를 표시
self.set_layout_visibility(self.admin_layout, True)
self.set_layout_visibility(self.user_layout, False)
else:
# 직원 모드: 관리자 PW를 숨기고, 직원 레이아웃을 표시
self.set_layout_visibility(self.admin_layout, False)
self.set_layout_visibility(self.user_layout, True)
def set_layout_visibility(self, changelayout, visible):
"""레이아웃에 포함된 모든 위젯의 가시성을 설정"""
for i in range(changelayout.count()):
widget = changelayout.itemAt(i).widget()
if widget:
widget.setVisible(visible)
def on_start_chrome_button_clicked(self):
"""크롬 실행 버튼 클릭 시 호출"""
self.logger.debug('크롬 실행 버튼 클릭됨')
# 비동기 함수 실행을 위해 asyncio.create_task 사용
asyncio.create_task(self.start_browser())
async def on_close_button_clicked(self):
"""크롬 실행 버튼 클릭 시 호출"""
self.logger.debug('크롬 실행 버튼 클릭됨')
# 비동기 함수 실행을 위해 asyncio.create_task 사용
task = asyncio.create_task(self.close())
await task # 작업이 완료될 때까지 대기
async def start_browser(self):
"""크롬 브라우저 실행 후 로그인"""
self.logger.debug('크롬 브라우저를 실행합니다...')
# await self.whale_translator.start_whale_browser()
await self.browser_controller.start_browser()
# 관리자 토글 상태에 따라 로그인
if self.admin_toggle.isChecked():
admin_id = self.admin_id_input.text()
admin_pw = self.admin_pw_input.text()
user_id = self.user_id_input.text()
user_pw = self.user_pw_input.text()
await self.browser_controller.login(admin_id, user_id, admin_pw, user_pw, is_admin=True)
else:
admin_id = self.admin_id_input.text()
admin_pw = self.admin_pw_input.text()
user_id = self.user_id_input.text()
user_pw = self.user_pw_input.text()
await self.browser_controller.login(admin_id, user_id, admin_pw, user_pw, is_admin=False)
# 로그인 정보 저장
self.save_settings()
# "신규 상품 등록" 페이지로 이동
self.logger.debug('신규 상품 등록 페이지로 이동 중...')
await self.browser_controller.go_to_new_product_page()
# 옵션핸들러에 초기화된 page 객체 전달.
self.optionHandler.update_page(self.browser_controller.page)
def save_settings(self):
"""QSettings에 사용자 정보 저장"""
self.settings.setValue("admin/id", self.admin_id_input.text())
self.settings.setValue("admin/pw", self.admin_pw_input.text())
self.settings.setValue("user/id", self.user_id_input.text())
self.settings.setValue("user/pw", self.user_pw_input.text())
self.settings.setValue("admin/toggle", self.admin_toggle.isChecked())
def load_settings(self):
"""QSettings에서 사용자 정보 불러오기"""
self.admin_id_input.setText(self.settings.value("admin/id", ""))
self.admin_pw_input.setText(self.settings.value("admin/pw", ""))
self.user_id_input.setText(self.settings.value("user/id", ""))
self.user_pw_input.setText(self.settings.value("user/pw", ""))
admin_toggle_state = self.settings.value("admin/toggle", "false") == "true"
self.admin_toggle.setChecked(admin_toggle_state)
self.on_toggle_clicked(admin_toggle_state)
def update_total_progress(self, current_value, total_value):
if current_value == 0:
self.total_progress_bar.setValue(0)
self.total_progress_bar.setFormat("상품 수정 대기") # current_value가 0일 때 표시될 텍스트
else:
# 프로그레스바의 값과 텍스트를 설정
percentage = int((current_value / total_value) * 100)
self.total_progress_bar.setValue(percentage)
self.total_progress_bar.setFormat(f"상품 {current_value}/{total_value}개 완료 [{percentage}%]")
def update_detail_progress(self, current_value, total_value):
if current_value == 0:
self.detail_progress_bar.setValue(0)
self.detail_progress_bar.setFormat("수정 대기") # current_value가 0일 때 표시될 텍스트
else:
# 프로그레스바의 값과 텍스트를 설정
percentage = int((current_value / total_value) * 100)
self.detail_progress_bar.setValue(percentage)
self.detail_progress_bar.setFormat(f"{current_value}/{total_value}개 완료 [{percentage}%]")
def on_start_translation_button_clicked(self):
"""번역 작업 버튼 클릭 시 호출"""
self.logger.debug('번역 작업 버튼 클릭됨')
# 비동기 함수 실행을 위해 asyncio.create_task 사용
asyncio.create_task(self.start_translation())
async def start_translation(self):
self.logger.debug('번역 작업을 시작합니다...')
self.running = True # 번역 작업이 시작됨
try:
# # 1. "신규 상품 등록" 페이지로 이동
# self.logger.debug('신규 상품 등록 페이지로 이동 중...')
# await self.browser_controller.go_to_new_product_page()
# 2. 총 상품 수 수집
await self.browser_controller.scroll_page_to_bottom() # 동적 로딩을 위해 끝까지 스크롤
total_products = await self.browser_controller.get_total_product_count()
if total_products == 0:
self.logger.debug('수집할 상품이 없습니다. 작업을 종료합니다.')
return
self.total_progress_bar.setMaximum(total_products)
self.total_progress_bar.setValue(0)
completed_count = 0
self.update_total_progress(completed_count, total_products)
page_number = 1
# 3. 총 상품 수만큼 반복 작업 수행
while self.running and completed_count < total_products:
self.logger.debug(f'현재 페이지: {page_number}')
if not page_number == 1:
self.browser_controller.scroll_page_to_top()
self.logger.debug(f'1페이지가 아니므로 동적로딩을 위해 휠 스크롤 업')
# 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기
product_buttons = await self.browser_controller.get_product_edit_buttons()
if not product_buttons:
self.logger.debug('수정할 상품이 없습니다. 작업을 종료합니다.')
break
# 5. 각 상품에 대해 번역 작업 수행
for index, button in enumerate(product_buttons, start=1):
if not self.running:
self.logger.debug('번역 작업이 중단되었습니다.')
return
self.logger.debug(f'{index}/{len(product_buttons)}: 세부사항 수정 작업 중...')
# 상품명 수집 및 수집 오류 처리
product_name = await self.browser_controller.get_product_name(index, 'xpath')
if product_name == "수집 오류 발생":
self.logger.debug('상품 수집 오류, 다음 상품으로 넘어갑니다.')
continue
# 상품 수정 다이얼로그 열기
await self.browser_controller.open_product_edit_dialog(button)
# 옵션 수정
self.start_stage(0)
await self.edit_option(product_name)
self.complete_stage(0)
# 가격 수정
# self.start_stage(0)
await self.edit_price()
# self.complete_stage(0)
# 상세페이지 수정
self.start_stage(1)
await self.detail_trans()
self.complete_stage(1)
# 수정 후 저장
self.logger.debug('상품 세부사항 저장 중...')
await self.browser_controller.save_and_ecs_product_edit()
completed_count += 1
self.update_total_progress(completed_count, total_products)
self.logger.debug(f'{completed_count}/[{total_products}]개 상품 수정 완료.')
if completed_count >= total_products:
self.logger.debug('모든 상품이 완료되었습니다.')
return
# 6. 다음 페이지로 이동 (있으면)
if not await self.browser_controller.go_to_next_page():
self.logger.debug('더 이상 페이지가 없습니다. 작업을 종료합니다.')
break
page_number += 1
if self.running:
self.logger.debug('모든 상품 번역 및 저장 완료.')
self.running = False # 작업 종료 후 상태를 False로 전환
except Exception as e:
self.logger.debug(f"번역 작업 중 오류 발생: {e}", exc_info=True)
self.running = False
# async def start_translation_ori(self):
# self.logger.debug('번역 작업을 시작합니다...')
# self.running = True # 번역 작업이 시작됨
# try:
# # # 1. "신규 상품 등록" 페이지로 이동
# # self.logger.debug('신규 상품 등록 페이지로 이동 중...')
# # await self.browser_controller.go_to_new_product_page()
# # 2. 총 상품 수 수집
# await self.browser_controller.scroll_page_to_bottom()
# total_products = await self.browser_controller.get_total_product_count()
# if total_products == 0:
# self.logger.debug('수집할 상품이 없습니다. 작업을 종료합니다.')
# return
# self.total_progress_bar.setMaximum(total_products)
# self.total_progress_bar.setValue(0)
# completed_count = 0
# self.update_total_progress(completed_count, total_products)
# page_number = 1
# while self.running or completed_count < total_products:
# # 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기
# self.logger.debug(f'현재 페이지: {page_number}')
# product_buttons = await self.browser_controller.get_product_edit_buttons()
# if not product_buttons:
# self.logger.debug('수정할 상품이 없습니다. 번역 작업을 종료합니다.')
# break
# # 5. 각 상품에 대해 번역 작업 수행
# for index, button in enumerate(product_buttons, start=1):
# if not self.running or completed_count >= total_products:
# self.logger.debug('번역 작업이 중단되었습니다.')
# break
# self.logger.debug(f'{index}/{len(product_buttons)}: 세부사항 수정 작업 중...')
# # 상품명 수집 및 수집 오류 처리
# product_name = await self.browser_controller.get_product_name(index)
# if product_name == "수집 오류 발생":
# self.logger.debug('상품 수집 오류, 다음 상품으로 넘어갑니다.')
# continue
# # 상품 수정 다이얼로그 열기
# await self.browser_controller.open_product_edit_dialog(button)
# # 옵션 수정
# self.start_stage(0)
# await self.edit_option()
# self.complete_stage(0)
# # 상세페이지 수정
# self.start_stage(1)
# await self.detail_trans()
# self.complete_stage(1)
# # 수정 후 저장
# self.logger.debug('상품 세부사항 저장 중...')
# await self.browser_controller.save_product_edit()
# completed_count += 1
# self.update_total_progress(completed_count, total_products)
# self.logger.debug(f'{completed_count}/[{total_products}]개 상품 수정 완료.')
# # 모든 상품이 완료되었는지 체크
# if completed_count >= total_products:
# self.logger.debug('모든 상품이 완료되었습니다.')
# break
# # 6. 다음 페이지로 이동 (있으면)
# if not await self.browser_controller.go_to_next_page():
# self.logger.debug('더 이상 페이지가 없습니다. 작업을 종료합니다.')
# break
# page_number += 1
# if self.running:
# self.logger.debug('모든 상품 번역 및 저장 완료.')
# self.running = False # 작업 종료 후 상태를 False로 전환
# except Exception as e:
# self.logger.debug(f"번역 작업 중 오류 발생: {e}", exc_info=True)
# self.running = False
def pause_translation(self):
self.logger.debug('번역 작업을 중단합니다...')
self.running = False # 번역 작업 중단
async def close(self):
self.logger.debug('프로그램을 종료합니다...')
self.save_settings()
await self.browser_controller.close_browser() # 브라우저 종료
self.whale_translator.close_all_virtual_desktops()
super().close()
async def detail_trans(self):
# 상세페이지 탭 클릭
await self.browser_controller.click_detail_tab()
# await self.browser_controller.page.wait_for_load_state('networkidle', timeout=10000)
self.detail_progress_bar.setValue(0)
self.detail_progress_bar.setVisible(True)
# 이미지 URL 추출
# image_urls = self.browser_controller.extract_image_urls()
image_urls = await self.browser_controller.extract_image_urls(self.optionHandler, is_option_data=True) # 코루틴 실행
total_images = len(image_urls)
self.logger.debug(f"현재 상품의 총 이미지 수 : {total_images}")
self.detail_image_count += total_images
# 이미지 번역 작업 진행
for i, url in enumerate(image_urls):
current_image_count = i +1
if not self.running:
self.logger.debug('번역 작업이 중단되었습니다.')
break
self.whale_translator.translate_image(url)
await self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url)
self.update_detail_progress(i,total_images)
current_image_count += 1
# 수정 후 저장
self.logger.debug('상품 세부사항 저장 중...')
await self.browser_controller.save_product_edit()
self.detail_progress_bar.setVisible(False)
self.detail_progress_bar.setValue(0)
async def edit_option(self, product_name):
# 상세페이지 탭 클릭
await self.browser_controller.click_option_tab()
# await self.browser_controller.page.wait_for_load_state('networkidle', timeout=10000)
self.detail_progress_bar.setVisible(True)
# 옵션 최대선택갯수
max_option_count = 20
option_image_trans = False
await self.optionHandler.process_options(product_name, max_option_count, option_image_trans)
# 수정 후 저장
# await self.optionHandler.save_option()
await self.browser_controller.save_product_edit()
self.detail_progress_bar.setVisible(False)
async def edit_price(self):
# 상세페이지 탭 클릭
await self.browser_controller.click_option_tab()
# await self.browser_controller.page.wait_for_load_state('networkidle', timeout=10000)
self.detail_progress_bar.setVisible(True)
# 가격 수정 프로세스
await self.priceHandler.process_price()
# 수정 후 저장
await self.browser_controller.save_product_edit()
self.detail_progress_bar.setVisible(False)