1067 lines
46 KiB
Python
1067 lines
46 KiB
Python
import sqlite3
|
|
import subprocess
|
|
import json
|
|
from datetime import datetime, time
|
|
from src.version import __version__
|
|
import xlrd
|
|
from PySide6.QtGui import QIcon
|
|
|
|
import logging
|
|
import traceback
|
|
import os
|
|
import sys
|
|
import winreg
|
|
import psutil
|
|
import subprocess
|
|
import pygetwindow as gw
|
|
import glob
|
|
import pandas as pd
|
|
from PySide6.QtWidgets import (
|
|
QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit, QButtonGroup, QRadioButton,
|
|
QComboBox, QSpinBox, QCheckBox, QProgressBar, QTextEdit, QFileDialog, QWidget, QMessageBox, QDialog, QTableWidget, QTableWidgetItem, QAbstractItemView, QDialogButtonBox
|
|
)
|
|
from PySide6.QtCore import Qt, QThread, Signal, QSettings
|
|
|
|
class BookmarkWorker(QThread):
|
|
progress = Signal(int)
|
|
log = Signal(str)
|
|
completed = Signal() # 작업 완료 시 호출
|
|
|
|
def __init__(self, bookmarks, folder_name, bookmarks_path, browser_path, selected_browser, remove_existing):
|
|
super().__init__()
|
|
self.bookmarks = bookmarks
|
|
self.folder_name = folder_name
|
|
self.bookmarks_path = bookmarks_path
|
|
self.browser_path = browser_path
|
|
self.selected_browser = selected_browser
|
|
self.remove_existing = remove_existing
|
|
self.chunk_size = 1000
|
|
|
|
def run(self):
|
|
try:
|
|
# JSON 파일 읽기
|
|
if not os.path.exists(self.bookmarks_path):
|
|
self.log.emit(f"즐겨찾기 JSON 파일을 찾을 수 없습니다: {self.bookmarks_path}")
|
|
return
|
|
|
|
try:
|
|
with open(self.bookmarks_path, "r", encoding="utf-8") as file:
|
|
file_content = file.read().strip()
|
|
if not file_content: # 파일이 비어 있는 경우
|
|
bookmarks_data = {"roots": {"bookmark_bar": {"children": []}}}
|
|
self.log.emit("JSON 파일이 비어 있어 기본값으로 초기화합니다.")
|
|
else:
|
|
bookmarks_data = json.loads(file_content)
|
|
except json.JSONDecodeError as e:
|
|
self.log.emit(f"JSON 파일 파싱 중 오류 발생: {str(e)}")
|
|
bookmarks_data = {"roots": {"bookmark_bar": {"children": []}}}
|
|
self.log.emit("JSON 파일을 기본값으로 초기화합니다.")
|
|
|
|
# 기존 북마크 제거
|
|
if self.remove_existing:
|
|
bookmarks_data["roots"]["bookmark_bar"] = self.remove_existing_bookmarks(
|
|
bookmarks_data["roots"]["bookmark_bar"]
|
|
)
|
|
|
|
# 북마크 추가
|
|
total_bookmarks = len(self.bookmarks)
|
|
bookmark_bar = bookmarks_data["roots"]["bookmark_bar"]
|
|
|
|
# 상위 폴더 이름 생성
|
|
current_time = datetime.now().strftime("%m-%d-%H-%M-%S") # 현재 날짜 및 시간 포맷
|
|
parent_folder_name = f"거상북마크-{current_time}" # 상위 폴더 이름
|
|
|
|
# 상위 폴더 생성
|
|
parent_folder = {
|
|
"type": "folder",
|
|
"name": parent_folder_name,
|
|
"children": []
|
|
}
|
|
|
|
# 하위 폴더 생성
|
|
chunk_size = self.chunk_size # 하위 폴더에 넣을 북마크 수
|
|
for idx, chunk_start in enumerate(range(0, total_bookmarks, chunk_size), start=1):
|
|
# 하위 폴더 이름 생성
|
|
folder_name = f"거상북마크-{self.folder_name}-{idx}" # 하위 폴더 이름 (예: 거상북마크-중국-1)
|
|
|
|
sub_folder = {
|
|
"type": "folder",
|
|
"name": folder_name,
|
|
"children": []
|
|
}
|
|
|
|
# 하위 폴더에 북마크 추가
|
|
for bookmark in self.bookmarks[chunk_start:chunk_start + chunk_size]:
|
|
sub_folder["children"].append({
|
|
"type": "url",
|
|
"name": bookmark['name'], # 몰 이름을 북마크 이름으로 설정
|
|
"url": bookmark['url']
|
|
})
|
|
|
|
# 상위 폴더에 하위 폴더 추가
|
|
parent_folder["children"].append(sub_folder)
|
|
|
|
# 진행률 업데이트
|
|
progress = int((chunk_start + len(self.bookmarks[chunk_start:chunk_start + chunk_size])) / total_bookmarks * 100)
|
|
self.progress.emit(progress)
|
|
|
|
# 즐겨찾기 바에 상위 폴더 추가
|
|
bookmark_bar["children"].append(parent_folder)
|
|
|
|
# 수정된 JSON 파일 저장
|
|
with open(self.bookmarks_path, "w", encoding="utf-8") as file:
|
|
json.dump(bookmarks_data, file, indent=4, ensure_ascii=False)
|
|
|
|
self.log.emit("즐겨찾기 추가 작업이 완료되었습니다!")
|
|
|
|
# 작업 완료 시 브라우저 실행
|
|
self.run_browser_with_profile(self.browser_path, self.bookmarks_path, browser_name=self.selected_browser)
|
|
|
|
# 작업 완료 후 '카피맨' 실행 및 창 활성화
|
|
self.run_and_focus_copyman()
|
|
|
|
self.completed.emit()
|
|
|
|
except Exception as e:
|
|
self.log.emit(f"오류 발생: {str(e)}", exc_info=True)
|
|
self.progress.emit(0)
|
|
|
|
# 작업 완료 시 브라우저 실행
|
|
def run_browser_with_profile(self, browser_path, bookmarks_path, browser_name="브라우저"):
|
|
|
|
if "웨일" in browser_name:
|
|
# 실행 중인 웨일 프로세스를 강제 종료
|
|
for proc in psutil.process_iter(attrs=["pid", "name"]):
|
|
if "whale" in proc.info["name"].lower():
|
|
try:
|
|
proc.terminate() # 프로세스 종료
|
|
proc.wait(timeout=5) # 종료 대기
|
|
self.log.emit("웨일 브라우저의 기존 프로세스를 종료했습니다.")
|
|
except Exception as e:
|
|
self.log.emit(f"웨일 브라우저 프로세스 종료 중 오류 발생: {str(e)}")
|
|
|
|
if os.path.exists(browser_path):
|
|
# 북마크 경로에서 프로필 이름 추출
|
|
profile_directory = os.path.basename(os.path.dirname(bookmarks_path))
|
|
|
|
# 접속 URL 설정
|
|
if "크롬" in browser_name.lower():
|
|
bookmark_page = "chrome://bookmarks/"
|
|
elif "웨일" in browser_name.lower():
|
|
bookmark_page = "whale://bookmarks/"
|
|
else:
|
|
bookmark_page = "about:blank" # 기본값 (알 수 없는 브라우저)
|
|
|
|
if profile_directory: # 유효한 프로필 디렉토리가 있는 경우
|
|
subprocess.Popen([
|
|
browser_path,
|
|
f"--profile-directory={profile_directory}",
|
|
bookmark_page
|
|
])
|
|
self.log.emit(f"{browser_name} 북마크 관리 페이지를 '{profile_directory}' 프로필로 열었습니다.")
|
|
else:
|
|
# 프로필 경로를 찾을 수 없는 경우 기본 실행
|
|
subprocess.Popen([browser_path, bookmark_page])
|
|
self.log.emit(f"{browser_name} 북마크 관리 페이지를 기본 프로필로 열었습니다.")
|
|
else:
|
|
self.log.emit(f"{browser_name} 실행 파일을 찾을 수 없습니다: {browser_path}")
|
|
|
|
def run_and_focus_copyman(self):
|
|
"""
|
|
'카피맨' 프로그램을 실행하고 해당 창으로 포커스를 전환하는 메서드
|
|
"""
|
|
program_name = "@카피맨.exe"
|
|
shortcut_name = "@카피맨"
|
|
window_title_start = "카피맨" # 창 제목이 "카피맨"으로 시작하는지 확인
|
|
|
|
try:
|
|
# 프로그램 실행 여부 확인
|
|
pid = self.is_program_running(program_name)
|
|
if pid:
|
|
self.log.emit(f"{program_name} 실행 중 (PID: {pid})")
|
|
# 실행 중인 프로그램 창으로 전환
|
|
if not self.focus_window_by_title(window_title_start):
|
|
self.log.emit(f"'{window_title_start}'로 시작하는 창을 찾을 수 없습니다.")
|
|
else:
|
|
self.log.emit(f"{program_name} 실행 중이 아님. 실행 시도 중...")
|
|
# 바로가기를 찾아 실행
|
|
shortcut_path = self.find_shortcut_in_start_menu(shortcut_name)
|
|
if shortcut_path:
|
|
self.run_program(shortcut_path)
|
|
else:
|
|
self.log.emit(f"'{shortcut_name}' 바로가기를 찾을 수 없습니다.")
|
|
except Exception as e:
|
|
error_traceback = traceback.format_exc()
|
|
self.log.emit(f"카피맨 실행 중 오류 발생: {str(e)}\n{error_traceback}")
|
|
|
|
def is_program_running(self, process_name):
|
|
"""프로세스 이름을 기준으로 프로그램 실행 여부 확인"""
|
|
for proc in psutil.process_iter(attrs=["pid", "name"]):
|
|
if process_name.lower() in proc.info["name"].lower():
|
|
return proc.info["pid"]
|
|
return None
|
|
|
|
def focus_window_by_title(self, title_start):
|
|
"""창 제목이 특정 문자열로 시작하는 창을 찾아 활성화"""
|
|
for window in gw.getAllWindows():
|
|
if window.title and window.title.startswith(title_start): # 창 제목이 조건에 맞는 경우
|
|
try:
|
|
window.activate()
|
|
self.log.emit(f"프로그램 창으로 전환: {window.title}")
|
|
return True
|
|
except Exception as e:
|
|
self.log.emit(f"창 활성화 실패: {e}")
|
|
return False
|
|
return False
|
|
|
|
def find_shortcut_in_start_menu(self, shortcut_name):
|
|
"""시작 메뉴에서 바로가기 찾기"""
|
|
user_start_menu = os.path.expandvars(r"%APPDATA%\Microsoft\Windows\Start Menu\Programs")
|
|
all_users_start_menu = os.path.expandvars(r"%ProgramData%\Microsoft\Windows\Start Menu\Programs")
|
|
for start_menu_path in [user_start_menu, all_users_start_menu]:
|
|
shortcut_path = glob.glob(os.path.join(start_menu_path, f"**\\{shortcut_name}.lnk"), recursive=True)
|
|
if shortcut_path:
|
|
return shortcut_path[0]
|
|
return None
|
|
|
|
def run_program(self, shortcut_path):
|
|
"""프로그램 실행"""
|
|
try:
|
|
subprocess.Popen([shortcut_path], shell=True)
|
|
self.log.emit(f"프로그램 실행: {shortcut_path}")
|
|
except Exception as e:
|
|
self.log.emit(f"프로그램 실행 실패: {e}")
|
|
|
|
def remove_existing_bookmarks(self, node):
|
|
"""
|
|
북마크 데이터를 재귀적으로 탐색하여 '거상북마크'로 시작하는 모든 폴더를 제거합니다.
|
|
:param node: 북마크 데이터의 현재 노드
|
|
:return: 필터링된 북마크 데이터
|
|
"""
|
|
if not isinstance(node, dict):
|
|
return node
|
|
|
|
# 폴더 이름이 '거상북마크'로 시작하면 제거
|
|
if node.get("type") == "folder" and node.get("name", "").startswith("거상북마크"):
|
|
self.log.emit(f"제거된 폴더: {node.get('name')}")
|
|
return None
|
|
|
|
# 자식(children)이 있는 경우 재귀적으로 탐색
|
|
if "children" in node:
|
|
node["children"] = [
|
|
self.remove_existing_bookmarks(child)
|
|
for child in node["children"]
|
|
if self.remove_existing_bookmarks(child) is not None
|
|
]
|
|
|
|
return node
|
|
|
|
class MainWindow(QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s")
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
# datetime을 문자열로 변환하는 어댑터 등록
|
|
sqlite3.register_adapter(datetime, lambda dt: dt.strftime("%Y-%m-%d %H:%M:%S"))
|
|
|
|
# 문자열을 datetime 객체로 변환하는 컨버터 등록
|
|
sqlite3.register_converter("DATETIME", lambda s: datetime.strptime(s.decode("utf-8"), "%Y-%m-%d %H:%M:%S"))
|
|
|
|
# 고정된 비밀번호
|
|
self.stored_password = "365"
|
|
|
|
# QSettings 초기화 (비밀번호 저장 및 불러오기용)
|
|
self.settings = QSettings("WhenRideMycar", "BookmarkAdder")
|
|
|
|
# 비밀번호 확인 창 표시
|
|
self.password = None
|
|
self.show_password_dialog()
|
|
|
|
# 프로그램 실행 가능 여부 확인
|
|
if self.password != self.stored_password:
|
|
QMessageBox.critical(self, "비밀번호 오류", "비밀번호가 일치하지 않습니다. 프로그램을 종료합니다.")
|
|
sys.exit()
|
|
|
|
self.setWindowTitle(f"크롬 즐겨찾기 추가 프로그램 (by 내차는언제타냐 feat.거상110+님) - v{__version__}")
|
|
self.setGeometry(300, 300, 800, 500)
|
|
|
|
# UI 구성
|
|
self.layout = QVBoxLayout()
|
|
self.buttons_layout = QHBoxLayout()
|
|
self.filter_layout = QHBoxLayout()
|
|
self.browser_selection_layout = QHBoxLayout()
|
|
|
|
# DB 입력 버튼
|
|
self.db_input_button = QPushButton("DB 입력")
|
|
self.db_input_button.setToolTip("엑셀 파일을 선택하여 DB에 저장합니다. 기존 DB를 제거하거나 추가로 데이터를 입력할 수 있습니다.")
|
|
self.db_input_button.clicked.connect(self.load_excel)
|
|
|
|
# DB 양식보기 버튼
|
|
self.sample_excel_button = QPushButton("추가양식 보기")
|
|
self.sample_excel_button.setToolTip("추가 양식을 확인합니다.")
|
|
self.sample_excel_button.clicked.connect(self.load_sample_excel)
|
|
|
|
# 기존 DB 제거 체크박스
|
|
self.remove_db_checkbox = QCheckBox("기존 DB 제거")
|
|
self.remove_db_checkbox.setToolTip("체크하면 기존 DB를 삭제하고 새롭게 데이터를 입력합니다.")
|
|
|
|
# 데이터 보기 버튼
|
|
self.view_data_button = QPushButton("데이터 보기")
|
|
self.view_data_button.setToolTip("DB 내용을 테이블 형식으로 표시하고 데이터를 수정할 수 있습니다.")
|
|
self.view_data_button.setEnabled(False)
|
|
self.view_data_button.clicked.connect(self.view_data)
|
|
|
|
# 브라우저 경로 설정 버튼
|
|
self.chrome_path_button = QPushButton("크롬 경로 설정")
|
|
self.chrome_path_button.clicked.connect(self.set_chrome_path)
|
|
|
|
self.whale_path_button = QPushButton("웨일 경로 설정")
|
|
self.whale_path_button.clicked.connect(self.set_whale_path)
|
|
|
|
# 브라우저 선택 라디오버튼
|
|
self.chrome_radio = QRadioButton("크롬")
|
|
self.whale_radio = QRadioButton("웨일")
|
|
self.whale_radio.clicked.connect(self.whale_radio_clicked)
|
|
self.browser_group = QButtonGroup()
|
|
self.browser_group.addButton(self.chrome_radio)
|
|
self.browser_group.addButton(self.whale_radio)
|
|
self.chrome_radio.setChecked(True) # 기본값: 크롬
|
|
|
|
# 드롭다운: 국가
|
|
self.country_label = QLabel("국가")
|
|
self.country_dropdown = QComboBox()
|
|
self.country_dropdown.addItems(["미국", "유럽", "중국", "일본", "한국", "기타", "랜덤"])
|
|
self.country_dropdown.setCurrentText("중국")
|
|
|
|
# 드롭다운: 등급
|
|
self.grade_label = QLabel("등급")
|
|
self.grade_dropdown = QComboBox()
|
|
self.grade_dropdown.addItems(["일반", "파워", "빅파워", "랜덤"])
|
|
self.grade_dropdown.setCurrentText("랜덤")
|
|
|
|
# 스핀박스: 추출 몰 갯수
|
|
self.count_label = QLabel("추출 몰 갯수")
|
|
self.count_spinbox = QSpinBox()
|
|
self.count_spinbox.setMinimum(1000)
|
|
self.count_spinbox.setMaximum(50000)
|
|
self.count_spinbox.setSingleStep(1000)
|
|
self.count_spinbox.setValue(1000)
|
|
|
|
# 스핀박스: 폴더당 북마크 갯수
|
|
self.fcount_label = QLabel("폴더당 북마크 갯수")
|
|
self.fcount_spinbox = QSpinBox()
|
|
self.fcount_spinbox.setMinimum(1000)
|
|
self.fcount_spinbox.setMaximum(50000)
|
|
self.fcount_spinbox.setSingleStep(100)
|
|
self.fcount_spinbox.setValue(100)
|
|
self.fcount_spinbox.valueChanged.connect(self.update_chunk_size)
|
|
|
|
# 기존 북마크 제거 체크박스
|
|
self.remove_existing_checkbox = QCheckBox("기존 북마크 제거")
|
|
self.remove_existing_checkbox.setToolTip("체크하면 '거상북마크'로 시작하는 모든 북마크를 제거합니다.")
|
|
|
|
# 실행 버튼
|
|
self.run_button = QPushButton("실행")
|
|
self.run_button.setToolTip("선택된 필터 조건에 따라 DB에서 데이터를 가져와 북마크를 추가합니다.")
|
|
self.run_button.clicked.connect(self.run_task)
|
|
|
|
# 추출 옵션 체크박스
|
|
self.extract_based_checkbox = QCheckBox("추출 횟수 기반 추출")
|
|
self.extract_based_checkbox.setChecked(False) # 기본값 OFF
|
|
self.extract_based_checkbox.setToolTip("체크 시 추출 횟수가 낮은 데이터부터 추출합니다.")
|
|
self.extract_based_checkbox.stateChanged.connect(self.toggle_extract_options)
|
|
|
|
# 최대 추출 횟수 스핀박스
|
|
self.max_extract_label = QLabel("최대 추출 횟수")
|
|
self.max_extract_spinbox = QSpinBox()
|
|
self.max_extract_spinbox.setMinimum(1)
|
|
self.max_extract_spinbox.setMaximum(5)
|
|
self.max_extract_spinbox.setValue(1) # 기본값
|
|
|
|
# 리셋 버튼
|
|
self.reset_button = QPushButton("추출 횟수 초기화")
|
|
self.reset_button.setToolTip("DB의 모든 추출 횟수를 0으로 초기화합니다.")
|
|
self.reset_button.clicked.connect(self.reset_extract_count)
|
|
|
|
# 로그 박스
|
|
self.log_box = QTextEdit()
|
|
self.log_box.setReadOnly(True)
|
|
|
|
# 프로그레스 바
|
|
self.progress_bar = QProgressBar()
|
|
|
|
# 필터 레이아웃 구성
|
|
self.filter_layout.addWidget(self.country_label)
|
|
self.filter_layout.addWidget(self.country_dropdown)
|
|
self.filter_layout.addWidget(QLabel(" "))
|
|
self.filter_layout.addWidget(self.grade_label)
|
|
self.filter_layout.addWidget(self.grade_dropdown)
|
|
self.filter_layout.addWidget(QLabel(" "))
|
|
self.filter_layout.addWidget(self.count_label)
|
|
self.filter_layout.addWidget(self.count_spinbox)
|
|
self.filter_layout.addWidget(QLabel(" "))
|
|
self.filter_layout.addWidget(self.fcount_label)
|
|
self.filter_layout.addWidget(self.fcount_spinbox)
|
|
self.filter_layout.addWidget(QLabel(" "))
|
|
self.filter_layout.addWidget(self.remove_existing_checkbox)
|
|
|
|
|
|
# # 버튼 레이아웃 구성
|
|
# self.buttons_layout.addWidget(self.chrome_path_button)
|
|
# self.buttons_layout.addWidget(self.whale_path_button)
|
|
|
|
# 브라우저 선택 레이아웃 구성
|
|
self.browser_selection_layout.addWidget(QLabel("브라우저 선택:"))
|
|
self.browser_selection_layout.addWidget(self.chrome_radio)
|
|
self.browser_selection_layout.addWidget(self.whale_radio)
|
|
|
|
self.browser_selection_layout.addWidget(self.chrome_path_button)
|
|
self.browser_selection_layout.addWidget(self.whale_path_button)
|
|
|
|
self.buttons_layout.addWidget(self.db_input_button)
|
|
self.buttons_layout.addWidget(self.sample_excel_button) # 추가 양식 버튼 추가
|
|
self.buttons_layout.addWidget(self.remove_db_checkbox)
|
|
self.buttons_layout.addWidget(self.view_data_button)
|
|
self.buttons_layout.addWidget(self.run_button)
|
|
|
|
self.filter_layout.addWidget(self.extract_based_checkbox)
|
|
self.filter_layout.addWidget(self.max_extract_label)
|
|
self.filter_layout.addWidget(self.max_extract_spinbox)
|
|
self.filter_layout.addWidget(self.reset_button)
|
|
|
|
# 메인 레이아웃 구성
|
|
self.layout.addLayout(self.filter_layout)
|
|
self.layout.addLayout(self.buttons_layout)
|
|
self.layout.addLayout(self.browser_selection_layout)
|
|
self.layout.addWidget(self.log_box)
|
|
self.layout.addWidget(self.progress_bar)
|
|
|
|
# 메인 위젯 설정
|
|
self.main_widget = QWidget()
|
|
self.main_widget.setLayout(self.layout)
|
|
self.setCentralWidget(self.main_widget)
|
|
|
|
# 모던한 디자인을 위한 스타일시트 적용
|
|
self.setStyleSheet("""
|
|
QMainWindow { background-color: #f9f9f9; }
|
|
QPushButton { background-color: #4CAF50; color: white; padding: 6px 12px; border-radius: 4px; }
|
|
QPushButton:hover { background-color: #45a049; }
|
|
QLineEdit, QComboBox, QSpinBox, QCheckBox, QTextEdit { font-size: 14px; }
|
|
QLabel { font-size: 14px; }
|
|
QProgressBar { height: 20px; }
|
|
""")
|
|
|
|
# 상태 변수
|
|
self.db_path = "markets.db"
|
|
|
|
# DB 파일 확인
|
|
self.check_db()
|
|
|
|
# 브라우저 선택 복원
|
|
self.restore_browser_selection()
|
|
|
|
# 브라우저 선택 상태 변경 시 QSettings에 저장
|
|
self.chrome_radio.toggled.connect(self.save_browser_selection)
|
|
self.whale_radio.toggled.connect(self.save_browser_selection)
|
|
|
|
|
|
# 크롬/웨일 경로 초기화
|
|
self.chrome_path = self.get_browser_path("chrome") or "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
|
self.chrome_bookmarks_path = os.path.join(
|
|
self.get_specific_profile_path("chrome"), "Bookmarks"
|
|
) if self.get_specific_profile_path("chrome") else ""
|
|
|
|
self.whale_path = self.get_browser_path("whale") or "C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe"
|
|
self.whale_bookmarks_path = os.path.join(
|
|
self.get_specific_profile_path("whale"), "Bookmarks"
|
|
) if self.get_specific_profile_path("whale") else ""
|
|
|
|
self.log(f"크롬 실행 파일 경로가 설정되었습니다: {self.chrome_path}")
|
|
self.log(f"웨일 실행 파일 경로가 설정되었습니다: {self.whale_path}")
|
|
|
|
self.log(f"크롬 프로파일 경로가 설정되었습니다: {self.chrome_bookmarks_path}")
|
|
self.log(f"웨일 프로파일 경로가 설정되었습니다: {self.whale_bookmarks_path}")
|
|
|
|
# 아이콘 설정 (추가된 부분)
|
|
self.set_app_icon()
|
|
|
|
def update_chunk_size(self):
|
|
"""폴더당 북마크 갯수 변경 시 chunk_size 업데이트"""
|
|
# self.log(f"폴더당 북마크 갯수가 변경되었습니다: {self.worker.chunk_size}")
|
|
|
|
def set_app_icon(self):
|
|
"""애플리케이션 아이콘을 설정합니다."""
|
|
base_dir = self.get_base_dir()
|
|
icon_path = os.path.join(base_dir, "bookmaker.ico")
|
|
if os.path.exists(icon_path):
|
|
self.setWindowIcon(QIcon(icon_path))
|
|
else:
|
|
pass
|
|
|
|
# 브라우저 실행 파일 경로 가져오기
|
|
def get_browser_path(self, browser_name):
|
|
try:
|
|
reg_path = r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"
|
|
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, f"{reg_path}\\{browser_name}.exe") as key:
|
|
return winreg.QueryValue(key, None)
|
|
except FileNotFoundError:
|
|
return None
|
|
|
|
# 브라우저 프로파일 경로 가져오기
|
|
def get_browser_profile_path(self, browser_name):
|
|
if browser_name == "chrome":
|
|
return os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\User Data")
|
|
elif browser_name == "whale":
|
|
return os.path.expandvars(r"%LOCALAPPDATA%\Naver\Naver Whale\User Data")
|
|
return None
|
|
|
|
# 기본 프로파일 경로 가져오기
|
|
def get_specific_profile_path(self, browser_name, profile_name="Default"):
|
|
"""
|
|
주어진 브라우저 이름에 따라 프로필 경로를 찾고 Bookmarks 파일이 존재하는 폴더를 반환합니다.
|
|
:param browser_name: 브라우저 이름 ("chrome" 또는 "whale")
|
|
:param profile_name: 기본 프로필 이름 (기본값은 "Default")
|
|
:return: Bookmarks 파일이 존재하는 프로필 경로 또는 None
|
|
"""
|
|
base_profile_path = self.get_browser_profile_path(browser_name)
|
|
if not base_profile_path:
|
|
return None
|
|
|
|
# 확인 순서: Default -> Profile 1 -> Profile 2 -> ...
|
|
profile_names = [profile_name] + [f"Profile {i}" for i in range(1, 10)] # 최대 10개의 프로필 확인
|
|
for profile in profile_names:
|
|
specific_profile_path = os.path.join(base_profile_path, profile)
|
|
bookmarks_file = os.path.join(specific_profile_path, "Bookmarks")
|
|
if os.path.exists(bookmarks_file):
|
|
self.log(f"'{profile}' 프로필에서 Bookmarks 파일을 찾았습니다: {bookmarks_file}")
|
|
return specific_profile_path
|
|
|
|
# Bookmarks 파일이 없는 경우 None 반환
|
|
self.log(f"'{browser_name}' 브라우저에서 유효한 Bookmarks 파일을 찾을 수 없습니다.")
|
|
return None
|
|
|
|
|
|
def set_chrome_path(self):
|
|
"""크롬 실행 파일 경로 설정"""
|
|
file_path, _ = QFileDialog.getOpenFileName(self, "크롬 실행 파일 선택", "", "Executable Files (*.exe)")
|
|
if file_path:
|
|
self.chrome_path = file_path
|
|
self.log(f"크롬 실행 파일 경로가 설정되었습니다: {file_path}")
|
|
|
|
def set_whale_path(self):
|
|
"""웨일 실행 파일 경로 설정"""
|
|
file_path, _ = QFileDialog.getOpenFileName(self, "웨일 실행 파일 선택", "", "Executable Files (*.exe)")
|
|
if file_path:
|
|
self.whale_path = file_path
|
|
self.log(f"웨일 실행 파일 경로가 설정되었습니다: {file_path}")
|
|
|
|
def show_password_dialog(self):
|
|
"""비밀번호 입력 창을 표시"""
|
|
dialog = QDialog(self)
|
|
dialog.setWindowTitle("비밀번호 입력")
|
|
dialog.setFixedSize(300, 150)
|
|
|
|
layout = QVBoxLayout(dialog)
|
|
|
|
# 비밀번호 입력 필드
|
|
password_label = QLabel("비밀번호:")
|
|
password_input = QLineEdit()
|
|
password_input.setEchoMode(QLineEdit.Password)
|
|
|
|
# 저장된 비밀번호 불러오기
|
|
saved_password = self.settings.value("password", "")
|
|
if saved_password:
|
|
password_input.setText(saved_password)
|
|
|
|
# 비밀번호 저장 체크박스
|
|
save_password_checkbox = QCheckBox("비밀번호 저장")
|
|
save_password_checkbox.setChecked(bool(saved_password)) # 저장된 비밀번호가 있으면 체크
|
|
|
|
# 확인 버튼
|
|
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, parent=dialog)
|
|
button_box.accepted.connect(dialog.accept)
|
|
button_box.rejected.connect(dialog.reject)
|
|
|
|
# 레이아웃 구성
|
|
layout.addWidget(password_label)
|
|
layout.addWidget(password_input)
|
|
layout.addWidget(save_password_checkbox)
|
|
layout.addWidget(button_box)
|
|
|
|
# 다이얼로그 실행
|
|
if dialog.exec() == QDialog.Accepted:
|
|
self.password = password_input.text()
|
|
|
|
# 비밀번호 저장 처리
|
|
if save_password_checkbox.isChecked():
|
|
self.settings.setValue("password", self.password)
|
|
else:
|
|
self.settings.remove("password")
|
|
else:
|
|
self.password = None
|
|
|
|
def save_browser_selection(self):
|
|
"""사용자가 선택한 브라우저를 QSettings에 저장"""
|
|
if self.chrome_radio.isChecked():
|
|
self.settings.setValue("selected_browser", "chrome")
|
|
self.log("사용자가 크롬 브라우저를 선택했습니다.")
|
|
elif self.whale_radio.isChecked():
|
|
self.settings.setValue("selected_browser", "whale")
|
|
self.log("사용자가 웨일 브라우저를 선택했습니다.")
|
|
|
|
def restore_browser_selection(self):
|
|
"""QSettings에서 저장된 브라우저 선택 상태를 복원"""
|
|
selected_browser = self.settings.value("selected_browser", "chrome") # 기본값은 크롬
|
|
if selected_browser == "chrome":
|
|
self.chrome_radio.setChecked(True)
|
|
elif selected_browser == "whale":
|
|
self.whale_radio.setChecked(True)
|
|
self.log(f"저장된 브라우저 선택을 복원했습니다: {selected_browser}")
|
|
|
|
def log(self, message, exc_info=False):
|
|
"""
|
|
메시지를 로그에 출력하고, GUI 로그 박스에도 표시
|
|
:param message: 출력할 메시지
|
|
:param exc_info: True일 경우 traceback 정보 포함
|
|
"""
|
|
if exc_info:
|
|
# traceback 정보 포함하여 로깅
|
|
self.logger.error(message, exc_info=True)
|
|
error_traceback = traceback.format_exc()
|
|
self.log_box.append(f"{message}\n{error_traceback}")
|
|
else:
|
|
self.logger.info(message)
|
|
self.log_box.append(message)
|
|
|
|
def check_db(self):
|
|
if not os.path.exists(self.db_path):
|
|
QMessageBox.warning(self, "DB 없음", "DB 파일이 없습니다. 엑셀을 지정하여 DB를 생성해주세요.")
|
|
else:
|
|
self.log("DB 파일이 로드되었습니다.")
|
|
self.view_data_button.setEnabled(True)
|
|
|
|
def get_base_dir(self):
|
|
"""
|
|
실행 환경에 따라 base_dir을 설정하는 메서드.
|
|
cx_Freeze로 패키징된 경우 실행 파일의 경로, 일반 Python 환경일 경우 __file__을 기준으로 설정.
|
|
"""
|
|
if getattr(sys, 'frozen', False): # 패키징된 경우
|
|
base_dir = os.path.dirname(sys.executable)
|
|
internal_dir = os.path.join(base_dir, 'lib', 'src') # lib 디렉토리 포함
|
|
if os.path.exists(internal_dir): # lib 디렉토리가 존재하면 base_dir로 설정
|
|
return internal_dir
|
|
|
|
else: # 일반 Python 실행 환경
|
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
debug_dir = os.path.join(base_dir, 'src') # lib 디렉토리 포함
|
|
|
|
return debug_dir
|
|
|
|
def load_sample_excel(self):
|
|
"""'추가양식.xlsx' 파일을 엽니다."""
|
|
try:
|
|
base_dir = self.get_base_dir()
|
|
file_path = os.path.join(base_dir, "추가양식.xlsx")
|
|
if os.path.exists(file_path):
|
|
os.startfile(file_path)
|
|
self.log(f"추가 양식 파일 열기: {file_path}")
|
|
else:
|
|
self.log(f"추가 양식 파일을 찾을 수 없습니다: {file_path}")
|
|
QMessageBox.warning(self, "파일 없음", "추가 양식 파일을 찾을 수 없습니다.")
|
|
except Exception as e:
|
|
self.log(f"추가 양식 파일 열기 중 오류 발생: {str(e)}", exc_info=True)
|
|
QMessageBox.critical(self, "오류", f"추가 양식 파일을 열 수 없습니다: {str(e)}")
|
|
|
|
def load_excel(self):
|
|
file_path, _ = QFileDialog.getOpenFileName(self, "엑셀 파일 선택", "", "Excel Files (*.xlsx *.xls)")
|
|
if not file_path:
|
|
return
|
|
|
|
try:
|
|
self.log("엑셀 파일을 불러오는 중...")
|
|
|
|
# 파일 확장자 확인
|
|
ext = os.path.splitext(file_path)[-1].lower()
|
|
|
|
# 열 이름 매핑 정의
|
|
column_name_mapping = {
|
|
'mall_url': ['mall_url(필수)', 'mall_url', '마켓링크', 'URL'],
|
|
'mall_name': ['mall_name', '셀러명', '판매자명'],
|
|
'mall_grade': ['mall_grade', '등급', '판매자등급'],
|
|
'country': ['country', '국가', '지역']
|
|
}
|
|
|
|
required_columns = ['mall_url']
|
|
all_expected_columns = list(column_name_mapping.keys())
|
|
|
|
# 엑셀 파일 읽기
|
|
if ext == ".xls":
|
|
try:
|
|
import xlrd
|
|
df = pd.read_excel(file_path, sheet_name=0, engine="xlrd")
|
|
except ImportError:
|
|
self.log("xlrd 라이브러리가 필요합니다. 'pip install xlrd>=2.0.1' 명령으로 설치하세요.")
|
|
return
|
|
except xlrd.biffh.XLRDError as e:
|
|
self.log(f"엑셀 파일을 열 수 없습니다. 파일 형식을 확인하세요: {e}")
|
|
return
|
|
elif ext == ".xlsx":
|
|
try:
|
|
df = pd.read_excel(file_path, sheet_name=0, engine="openpyxl")
|
|
except Exception as e:
|
|
self.log(f"엑셀 파일을 열 수 없습니다: {e}", exc_info=True)
|
|
return
|
|
else:
|
|
self.log("지원되지 않는 파일 형식입니다. .xls 또는 .xlsx 파일을 선택하세요.")
|
|
return
|
|
|
|
# 엑셀 열 이름과 정의된 이름 매핑
|
|
rename_dict = {}
|
|
excel_columns = df.columns.tolist()
|
|
for internal_name, possible_names in column_name_mapping.items():
|
|
for excel_col in excel_columns:
|
|
if excel_col in possible_names:
|
|
rename_dict[excel_col] = internal_name
|
|
break # 하나라도 매칭되면 다음 내부 이름으로 이동
|
|
|
|
# 데이터프레임 열 이름 변경
|
|
df.rename(columns=rename_dict, inplace=True)
|
|
|
|
# 필수 열이 있는지 확인
|
|
if 'mall_url' not in df.columns:
|
|
self.log(f"엑셀 파일에 필수 열({column_name_mapping['mall_url']}) 중 하나라도 있어야 합니다.")
|
|
return
|
|
|
|
# 필요한 열만 선택 (매핑된 내부 이름 사용)
|
|
available_columns = [col for col in all_expected_columns if col in df.columns]
|
|
df = df[available_columns].copy()
|
|
|
|
# 누락된 열은 빈 값으로 채우기
|
|
for col in all_expected_columns:
|
|
if col not in df.columns:
|
|
df[col] = ""
|
|
|
|
# 열 순서 설정 (데이터베이스 테이블 순서와 맞춤)
|
|
df = df[['country', 'mall_grade', 'mall_name', 'mall_url']]
|
|
|
|
# datetime.time 타입 데이터를 문자열로 변환
|
|
def convert_time_to_string(x):
|
|
try:
|
|
if isinstance(x, time): # 시간이면 변환
|
|
return x.strftime("%H:%M:%S")
|
|
return x # 아니면 그대로 반환
|
|
except Exception as e:
|
|
self.log(f"데이터 변환 중 에러 발생: {str(e)}")
|
|
return x
|
|
|
|
if 'mall_name' in df.columns:
|
|
df['mall_name'] = df['mall_name'].apply(convert_time_to_string)
|
|
if 'mall_url' in df.columns:
|
|
df['mall_url'] = df['mall_url'].apply(convert_time_to_string)
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
if self.remove_db_checkbox.isChecked():
|
|
# 기존 DB 제거 및 새 테이블 생성
|
|
conn.execute("DROP TABLE IF EXISTS markets")
|
|
conn.execute("""
|
|
CREATE TABLE markets (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
country TEXT,
|
|
mall_grade TEXT,
|
|
mall_name TEXT,
|
|
mall_url TEXT,
|
|
extract_count INTEGER DEFAULT 0
|
|
)
|
|
""")
|
|
self.log("기존 DB가 제거되었습니다. 새롭게 생성되었습니다.")
|
|
|
|
# 기존 DB에 `extract_count` 필드 추가
|
|
try:
|
|
conn.execute("ALTER TABLE markets ADD COLUMN extract_count INTEGER DEFAULT 0")
|
|
except sqlite3.OperationalError:
|
|
# 컬럼이 이미 있는 경우 무시
|
|
pass
|
|
|
|
added_count = 0
|
|
skipped_count = 0
|
|
|
|
# 데이터 저장 및 중복 방지
|
|
for _, row in df.iterrows():
|
|
mall_url = row['mall_url']
|
|
cursor.execute("SELECT mall_url FROM markets WHERE mall_url=?", (mall_url,))
|
|
existing_mall = cursor.fetchone()
|
|
if not existing_mall:
|
|
cursor.execute("""
|
|
INSERT INTO markets (country, mall_grade, mall_name, mall_url)
|
|
VALUES (?, ?, ?, ?)
|
|
""", (row['country'], row['mall_grade'], row['mall_name'], mall_url))
|
|
self.log(f"새로운 몰 추가: {mall_url}")
|
|
added_count += 1
|
|
else:
|
|
self.log(f"중복된 몰 건너뛰기: {mall_url}")
|
|
skipped_count += 1
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
self.log("DB 저장 완료")
|
|
|
|
self.show_load_excel_result_message(added_count, skipped_count)
|
|
|
|
except Exception as e:
|
|
self.log(f"엑셀 파일을 불러오는 중 에러 발생: {str(e)}", exc_info=True)
|
|
|
|
def show_load_excel_result_message(self, added_count, skipped_count):
|
|
"""엑셀 로드 완료 후 결과를 메시지 박스로 보여줍니다."""
|
|
QMessageBox.information(
|
|
self,
|
|
"엑셀 처리 완료",
|
|
f"총 {added_count}개의 몰이 추가되었고, {skipped_count}개의 중복된 몰은 제외되었습니다."
|
|
)
|
|
|
|
def view_data(self):
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
query = "SELECT * FROM markets"
|
|
df = pd.read_sql_query(query, conn)
|
|
conn.close()
|
|
|
|
dialog = QDialog(self)
|
|
dialog.setWindowTitle("DB 데이터 보기")
|
|
dialog.setGeometry(100, 100, 800, 400)
|
|
|
|
table = QTableWidget(dialog)
|
|
table.setRowCount(len(df))
|
|
table.setColumnCount(len(df.columns))
|
|
table.setHorizontalHeaderLabels(df.columns)
|
|
|
|
for i, row in df.iterrows():
|
|
for j, value in enumerate(row):
|
|
item = QTableWidgetItem(str(value))
|
|
if j == 0: # id 열 (편집 불가능)
|
|
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
|
|
table.setItem(i, j, item)
|
|
|
|
# 네 번째 열 너비 조정
|
|
table.setColumnWidth(4, table.columnWidth(3) * 2)
|
|
|
|
# 테이블 정렬 활성화
|
|
table.setSortingEnabled(True)
|
|
|
|
# 정렬을 위한 헤더 클릭 이벤트 연결
|
|
table.horizontalHeader().sectionClicked.connect(lambda index: self.sort_table(table, index))
|
|
|
|
table.cellChanged.connect(lambda: self.confirm_edit(table))
|
|
table.resize(780, 380)
|
|
dialog.exec()
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "오류", f"DB 데이터를 불러오는 중 오류 발생: {e}", exc_info=True)
|
|
|
|
# 웨일 라디오 버튼 클릭 이벤트 처리 메서드
|
|
def whale_radio_clicked(self):
|
|
# 메세지 창 띄우기
|
|
QMessageBox.warning(
|
|
self,
|
|
"주의!!",
|
|
"웨일의 경우 현재 실행된 웨일브라우저를 강제종료합니다."
|
|
)
|
|
|
|
def sort_table(self, table, column_index):
|
|
"""
|
|
테이블 데이터를 정렬하는 함수
|
|
:param table: QTableWidget
|
|
:param column_index: 정렬할 열 인덱스
|
|
"""
|
|
try:
|
|
order = table.horizontalHeader().sortIndicatorOrder() # 현재 정렬 방향 확인
|
|
table.sortItems(column_index, order) # 해당 열에 따라 정렬
|
|
self.log(f"{column_index + 1}번째 열을 정렬했습니다. 방향: {'오름차순' if order == Qt.AscendingOrder else '내림차순'}")
|
|
except Exception as e:
|
|
self.log(f"테이블 정렬 중 오류 발생: {str(e)}", exc_info=True)
|
|
|
|
def confirm_edit(self, table):
|
|
reply = QMessageBox.question(self, "확인", "수정된 데이터를 저장하시겠습니까?", QMessageBox.Yes | QMessageBox.No)
|
|
if reply == QMessageBox.Yes:
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
for row in range(table.rowCount()):
|
|
# id는 수정하지 않으므로 첫 번째 열은 그대로 사용
|
|
record_id = table.item(row, 0).text()
|
|
country = table.item(row, 1).text()
|
|
mall_grade = table.item(row, 2).text()
|
|
mall_name = table.item(row, 3).text()
|
|
mall_url = table.item(row, 4).text()
|
|
|
|
# id를 기준으로 업데이트
|
|
cursor.execute("""
|
|
UPDATE markets
|
|
SET country = ?, mall_grade = ?, mall_name = ?, mall_url = ?
|
|
WHERE id = ?
|
|
""", (country, mall_grade, mall_name, mall_url, record_id))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
self.log("수정된 데이터가 저장되었습니다.")
|
|
else:
|
|
self.log("수정이 취소되었습니다.")
|
|
|
|
def ensure_extract_count_column(self, conn):
|
|
cursor = conn.cursor()
|
|
cursor.execute("PRAGMA table_info(markets)")
|
|
columns = [row[1] for row in cursor.fetchall()]
|
|
if "extract_count" not in columns:
|
|
conn.execute("ALTER TABLE markets ADD COLUMN extract_count INTEGER DEFAULT 0")
|
|
|
|
def toggle_extract_options(self):
|
|
if self.extract_based_checkbox.isChecked():
|
|
self.max_extract_spinbox.setEnabled(True)
|
|
else:
|
|
self.max_extract_spinbox.setEnabled(False)
|
|
|
|
def run_task(self):
|
|
country = self.country_dropdown.currentText()
|
|
grade = self.grade_dropdown.currentText()
|
|
count = self.count_spinbox.value()
|
|
remove_existing = self.remove_existing_checkbox.isChecked()
|
|
|
|
extract_based = self.extract_based_checkbox.isChecked()
|
|
max_extract = self.max_extract_spinbox.value()
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
|
|
try:
|
|
# 추출 횟수 필드 확인 및 추가
|
|
self.ensure_extract_count_column(conn)
|
|
|
|
# 기본 쿼리
|
|
query = "SELECT id, mall_name AS name, mall_url AS url FROM markets WHERE 1=1"
|
|
|
|
# 국가 필터
|
|
if country != "랜덤":
|
|
query += f" AND country = '{country}'"
|
|
# 등급 필터
|
|
if grade != "랜덤":
|
|
query += f" AND mall_grade = '{grade}'"
|
|
|
|
# 추출 횟수 기반 옵션 처리
|
|
if extract_based:
|
|
query += " AND extract_count < ?"
|
|
query += " ORDER BY extract_count ASC, RANDOM()"
|
|
else:
|
|
query += " ORDER BY RANDOM()"
|
|
|
|
query += f" LIMIT {count}"
|
|
|
|
# 추출
|
|
if extract_based:
|
|
df = pd.read_sql_query(query, conn, params=(max_extract,))
|
|
else:
|
|
df = pd.read_sql_query(query, conn)
|
|
|
|
if df.empty:
|
|
# 추출 가능한 데이터가 없는 경우 리셋
|
|
if extract_based:
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"데이터 초기화",
|
|
"추출 가능한 데이터가 없습니다. 모든 추출 횟수를 초기화하시겠습니까?",
|
|
QMessageBox.Yes | QMessageBox.No
|
|
)
|
|
if reply == QMessageBox.Yes:
|
|
self.reset_extract_count()
|
|
else:
|
|
return
|
|
else:
|
|
QMessageBox.warning(self, "추출 실패", "DB에 추출 가능한 데이터가 없습니다.")
|
|
return
|
|
|
|
self.bookmarks = df.to_dict("records")
|
|
|
|
# 추출 횟수 업데이트
|
|
for record in df.to_dict("records"):
|
|
conn.execute("UPDATE markets SET extract_count = extract_count + 1 WHERE id = ?", (record["id"],))
|
|
conn.commit()
|
|
|
|
self.log(f"{len(self.bookmarks)}개의 북마크를 추출했습니다.")
|
|
|
|
except Exception as e:
|
|
self.log(f"DB 쿼리 실행 중 오류 발생: {str(e)}", exc_info=True)
|
|
conn.close()
|
|
return
|
|
|
|
conn.close()
|
|
|
|
|
|
folder_name = f"거상북마크-{grade}"
|
|
|
|
# 사용자가 선택한 브라우저에 따라 경로 설정
|
|
if self.chrome_radio.isChecked():
|
|
selected_bookmarks_path = self.chrome_bookmarks_path
|
|
selected_browser_path = self.chrome_path
|
|
selected_browser = '크롬'
|
|
self.log("크롬 브라우저가 선택되었습니다.")
|
|
elif self.whale_radio.isChecked():
|
|
selected_bookmarks_path = self.whale_bookmarks_path
|
|
selected_browser_path = self.whale_path
|
|
selected_browser = '웨일'
|
|
self.log("웨일 브라우저가 선택되었습니다.")
|
|
else:
|
|
QMessageBox.warning(self, "브라우저 선택 오류", "브라우저를 선택해주세요.")
|
|
return
|
|
|
|
# 선택된 브라우저 경로 유효성 확인
|
|
if not os.path.exists(selected_browser_path):
|
|
QMessageBox.warning(self, "브라우저 경로 오류", "선택된 브라우저 실행 파일 경로가 유효하지 않습니다. 경로를 설정해주세요.")
|
|
return
|
|
|
|
if not os.path.exists(selected_bookmarks_path):
|
|
QMessageBox.warning(self, "북마크 경로 오류", "선택된 브라우저의 북마크 경로가 유효하지 않습니다. 경로를 설정해주세요.")
|
|
return
|
|
|
|
self.worker = BookmarkWorker(self.bookmarks, folder_name, selected_bookmarks_path, selected_browser_path, selected_browser, remove_existing)
|
|
|
|
self.worker.chunk_size = self.fcount_spinbox.value()
|
|
|
|
self.worker.progress.connect(self.progress_bar.setValue)
|
|
self.worker.log.connect(self.log)
|
|
self.worker.completed.connect(self.task_completed)
|
|
self.worker.start()
|
|
|
|
def task_completed(self):
|
|
self.log("작업이 완료되었습니다!")
|
|
QMessageBox.information(self, "완료", "즐겨찾기 추가 작업이 완료되었습니다.")
|
|
|
|
def reset_extract_count(self):
|
|
conn = sqlite3.connect(self.db_path)
|
|
try:
|
|
# `extract_count` 필드가 없으면 추가
|
|
try:
|
|
conn.execute("ALTER TABLE markets ADD COLUMN extract_count INTEGER DEFAULT 0")
|
|
except sqlite3.OperationalError:
|
|
# 이미 필드가 존재하는 경우 무시
|
|
pass
|
|
|
|
# 모든 추출 횟수를 0으로 초기화
|
|
conn.execute("UPDATE markets SET extract_count = 0")
|
|
conn.commit()
|
|
self.log("모든 추출 횟수가 초기화되었습니다.")
|
|
QMessageBox.information(self, "초기화 완료", "모든 추출 횟수가 초기화되었습니다.")
|
|
except Exception as e:
|
|
self.log(f"추출 횟수 초기화 중 오류 발생: {str(e)}", exc_info=True)
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def main():
|
|
app = QApplication(sys.argv)
|
|
window = MainWindow()
|
|
window.show()
|
|
sys.exit(app.exec())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|