1.3
This commit is contained in:
parent
664007167d
commit
f766f5706b
12
main.py
12
main.py
|
|
@ -2,6 +2,8 @@ import sqlite3
|
|||
import subprocess
|
||||
import json
|
||||
from datetime import datetime, time
|
||||
from src.version import __version__
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
import os
|
||||
|
|
@ -278,7 +280,7 @@ class MainWindow(QMainWindow):
|
|||
QMessageBox.critical(self, "비밀번호 오류", "비밀번호가 일치하지 않습니다. 프로그램을 종료합니다.")
|
||||
sys.exit()
|
||||
|
||||
self.setWindowTitle("크롬 즐겨찾기 추가 프로그램 (by 내차는언제타냐 feat.거상110+님)")
|
||||
self.setWindowTitle(f"크롬 즐겨찾기 추가 프로그램 (by 내차는언제타냐 feat.거상110+님) - v{__version__}")
|
||||
self.setGeometry(300, 300, 800, 500)
|
||||
|
||||
# UI 구성
|
||||
|
|
@ -333,10 +335,10 @@ class MainWindow(QMainWindow):
|
|||
# 스핀박스: 갯수
|
||||
self.count_label = QLabel("갯수")
|
||||
self.count_spinbox = QSpinBox()
|
||||
self.count_spinbox.setMinimum(100)
|
||||
self.count_spinbox.setMaximum(1000)
|
||||
self.count_spinbox.setSingleStep(100)
|
||||
self.count_spinbox.setValue(100)
|
||||
self.count_spinbox.setMinimum(1000)
|
||||
self.count_spinbox.setMaximum(50000)
|
||||
self.count_spinbox.setSingleStep(1000)
|
||||
self.count_spinbox.setValue(1000)
|
||||
|
||||
# 기존 북마크 제거 체크박스
|
||||
self.remove_existing_checkbox = QCheckBox("기존 북마크 제거")
|
||||
|
|
|
|||
5
setup.py
5
setup.py
|
|
@ -1,10 +1,11 @@
|
|||
from cx_Freeze import setup, Executable
|
||||
import os
|
||||
import sys
|
||||
from src.version import __version__
|
||||
|
||||
# 애플리케이션 이름과 버전
|
||||
application_name = "크롬 즐겨찾기 추가 프로그램"
|
||||
application_version = "1.2"
|
||||
application_version = __version__
|
||||
|
||||
# 실행 파일 생성 설정
|
||||
base = None
|
||||
|
|
@ -40,7 +41,7 @@ executables = [
|
|||
setup(
|
||||
name=application_name,
|
||||
version=application_version,
|
||||
description="크롬 즐겨찾기 추가 프로그램 (내차는언제타냐 feat.110+)",
|
||||
description=f"크롬 즐겨찾기 추가 프로그램 (내차는언제타냐 feat.110+ - v{__version__})",
|
||||
options={"build_exe": build_options},
|
||||
executables=executables,
|
||||
)
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
__version__ = "1.3.1"
|
||||
|
|
@ -0,0 +1,425 @@
|
|||
import flet as ft
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import json
|
||||
from datetime import datetime, time
|
||||
import threading
|
||||
import logging
|
||||
import traceback
|
||||
import os
|
||||
import sys
|
||||
import psutil
|
||||
import glob
|
||||
import pandas as pd
|
||||
|
||||
# 로그 기본 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# --- BookmarkWorker (백그라운드 작업) ---
|
||||
class BookmarkWorker(threading.Thread):
|
||||
def __init__(self, bookmarks, folder_name, bookmarks_path, browser_path, selected_browser, remove_existing, progress_callback, log_callback, completed_callback):
|
||||
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.progress_callback = progress_callback
|
||||
self.log_callback = log_callback
|
||||
self.completed_callback = completed_callback
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
# JSON 파일 읽기
|
||||
if not os.path.exists(self.bookmarks_path):
|
||||
self.log_callback(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_callback("JSON 파일이 비어 있어 기본값으로 초기화합니다.")
|
||||
else:
|
||||
bookmarks_data = json.loads(file_content)
|
||||
except json.JSONDecodeError as e:
|
||||
self.log_callback(f"JSON 파일 파싱 중 오류 발생: {str(e)}")
|
||||
bookmarks_data = {"roots": {"bookmark_bar": {"children": []}}}
|
||||
self.log_callback("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 단위)
|
||||
chunk_size = 100
|
||||
for idx, chunk_start in enumerate(range(0, total_bookmarks, chunk_size), start=1):
|
||||
folder_name = f"거상북마크-{self.folder_name}-{idx}"
|
||||
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_callback(progress)
|
||||
|
||||
bookmark_bar["children"].append(parent_folder)
|
||||
with open(self.bookmarks_path, "w", encoding="utf-8") as file:
|
||||
json.dump(bookmarks_data, file, indent=4, ensure_ascii=False)
|
||||
|
||||
self.log_callback("즐겨찾기 추가 작업이 완료되었습니다!")
|
||||
self.run_browser_with_profile(self.browser_path, self.bookmarks_path, browser_name=self.selected_browser)
|
||||
self.run_and_focus_copyman()
|
||||
self.completed_callback()
|
||||
|
||||
except Exception as e:
|
||||
self.log_callback(f"오류 발생: {str(e)}\n{traceback.format_exc()}")
|
||||
self.progress_callback(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_callback("웨일 브라우저의 기존 프로세스를 종료했습니다.")
|
||||
except Exception as e:
|
||||
self.log_callback(f"웨일 브라우저 프로세스 종료 중 오류 발생: {str(e)}")
|
||||
if os.path.exists(browser_path):
|
||||
profile_directory = os.path.basename(os.path.dirname(bookmarks_path))
|
||||
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_callback(f"{browser_name} 북마크 관리 페이지를 '{profile_directory}' 프로필로 열었습니다.")
|
||||
else:
|
||||
subprocess.Popen([browser_path, bookmark_page])
|
||||
self.log_callback(f"{browser_name} 북마크 관리 페이지를 기본 프로필로 열었습니다.")
|
||||
else:
|
||||
self.log_callback(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_callback(f"{program_name} 실행 중 (PID: {pid})")
|
||||
if not self.focus_window_by_title(window_title_start):
|
||||
self.log_callback(f"'{window_title_start}'로 시작하는 창을 찾을 수 없습니다.")
|
||||
else:
|
||||
self.log_callback(f"{program_name} 실행 중이 아님. 실행 시도 중...")
|
||||
shortcut_path = self.find_shortcut_in_start_menu(shortcut_name)
|
||||
if shortcut_path:
|
||||
self.run_program(shortcut_path)
|
||||
else:
|
||||
self.log_callback(f"'{shortcut_name}' 바로가기를 찾을 수 없습니다.")
|
||||
except Exception as e:
|
||||
self.log_callback(f"카피맨 실행 중 오류 발생: {str(e)}\n{traceback.format_exc()}")
|
||||
|
||||
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):
|
||||
try:
|
||||
import pygetwindow as gw
|
||||
for window in gw.getAllWindows():
|
||||
if window.title and window.title.startswith(title_start):
|
||||
try:
|
||||
window.activate()
|
||||
self.log_callback(f"프로그램 창으로 전환: {window.title}")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.log_callback(f"창 활성화 실패: {e}")
|
||||
return False
|
||||
return False
|
||||
except Exception as e:
|
||||
self.log_callback(f"윈도우 포커스 전환 오류: {e}")
|
||||
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_callback(f"프로그램 실행: {shortcut_path}")
|
||||
except Exception as e:
|
||||
self.log_callback(f"프로그램 실행 실패: {e}")
|
||||
|
||||
def remove_existing_bookmarks(self, node):
|
||||
if not isinstance(node, dict):
|
||||
return node
|
||||
if node.get("type") == "folder" and node.get("name", "").startswith("거상북마크"):
|
||||
self.log_callback(f"제거된 폴더: {node.get('name')}")
|
||||
return None
|
||||
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
|
||||
|
||||
# --- Flet 기반 메인 앱 ---
|
||||
def main(page: ft.Page):
|
||||
page.title = "즐겨찾기 추가 앱 (Flet 버전)"
|
||||
page.horizontal_alignment = "center"
|
||||
page.vertical_alignment = "start"
|
||||
page.padding = 20
|
||||
|
||||
# 전역 상태 변수 (필요에 따라 수정)
|
||||
db_path = "markets.db"
|
||||
bookmarks = [] # DB에서 추출한 북마크 목록
|
||||
chrome_path = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
||||
whale_path = "C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe"
|
||||
# 브라우저별 북마크 파일 경로 (사용자 환경에 맞게 설정 필요)
|
||||
chrome_bookmarks_path = "C:\\Users\\User\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Bookmarks"
|
||||
whale_bookmarks_path = "C:\\Users\\User\\AppData\\Local\\Naver\\Naver Whale\\User Data\\Default\\Bookmarks"
|
||||
stored_password = "365"
|
||||
user_password = None # 입력받은 비밀번호 저장
|
||||
|
||||
# --- 로그 업데이트 함수 ---
|
||||
log_display = ft.Text(value="", size=12)
|
||||
def log(msg: str):
|
||||
log_display.value += msg + "\n"
|
||||
page.update()
|
||||
|
||||
# --- 진행률 업데이트 함수 ---
|
||||
progress_bar = ft.ProgressBar(width=300, value=0)
|
||||
def update_progress(val: int):
|
||||
progress_bar.value = val / 100
|
||||
page.update()
|
||||
|
||||
# --- 작업 완료 후 호출 함수 ---
|
||||
def task_completed():
|
||||
log("작업이 완료되었습니다!")
|
||||
dlg = ft.AlertDialog(
|
||||
title=ft.Text("완료"),
|
||||
content=ft.Text("즐겨찾기 추가 작업이 완료되었습니다."),
|
||||
actions=[ft.TextButton("닫기", on_click=lambda e: close_dialog())],
|
||||
modal=True
|
||||
)
|
||||
page.dialog = dlg
|
||||
dlg.open = True
|
||||
page.update()
|
||||
|
||||
def close_dialog():
|
||||
page.dialog.open = False
|
||||
page.update()
|
||||
|
||||
# --- 비밀번호 입력 다이얼로그 (모달) ---
|
||||
def show_password_dialog():
|
||||
def on_password_submit(e):
|
||||
nonlocal user_password
|
||||
user_password = password_field.value
|
||||
if user_password != stored_password:
|
||||
result_dialog = ft.AlertDialog(
|
||||
title=ft.Text("비밀번호 오류"),
|
||||
content=ft.Text("비밀번호가 일치하지 않습니다. 프로그램을 종료합니다."),
|
||||
actions=[ft.TextButton("닫기", on_click=lambda e: page.window_close())],
|
||||
modal=True
|
||||
)
|
||||
page.dialog = result_dialog
|
||||
result_dialog.open = True
|
||||
page.update()
|
||||
else:
|
||||
pwd_dlg.open = False
|
||||
page.update()
|
||||
|
||||
password_field = ft.TextField(label="비밀번호", password=True)
|
||||
pwd_dlg = ft.AlertDialog(
|
||||
title=ft.Text("비밀번호 입력"),
|
||||
content=ft.Column([password_field]),
|
||||
actions=[ft.TextButton("확인", on_click=on_password_submit)],
|
||||
modal=True
|
||||
)
|
||||
page.dialog = pwd_dlg
|
||||
pwd_dlg.open = True
|
||||
page.update()
|
||||
|
||||
show_password_dialog()
|
||||
|
||||
# --- UI 컨트롤 ---
|
||||
country_dropdown = ft.Dropdown(
|
||||
label="국가",
|
||||
options=[
|
||||
ft.dropdown.Option("미국"),
|
||||
ft.dropdown.Option("유럽"),
|
||||
ft.dropdown.Option("중국"),
|
||||
ft.dropdown.Option("일본"),
|
||||
ft.dropdown.Option("한국"),
|
||||
ft.dropdown.Option("기타"),
|
||||
ft.dropdown.Option("랜덤")
|
||||
],
|
||||
value="중국"
|
||||
)
|
||||
|
||||
grade_dropdown = ft.Dropdown(
|
||||
label="등급",
|
||||
options=[
|
||||
ft.dropdown.Option("일반"),
|
||||
ft.dropdown.Option("파워"),
|
||||
ft.dropdown.Option("빅파워"),
|
||||
ft.dropdown.Option("랜덤")
|
||||
],
|
||||
value="랜덤"
|
||||
)
|
||||
|
||||
count_field = ft.TextField(label="갯수", value="1000", keyboard_type=ft.KeyboardType.NUMBER)
|
||||
remove_existing_checkbox = ft.Checkbox(label="기존 북마크 제거", value=False)
|
||||
extract_based_checkbox = ft.Checkbox(label="추출 횟수 기반 추출", value=False)
|
||||
max_extract_field = ft.TextField(label="최대 추출 횟수", value="1", keyboard_type=ft.KeyboardType.NUMBER)
|
||||
|
||||
browser_dropdown = ft.Dropdown(
|
||||
label="브라우저 선택",
|
||||
options=[
|
||||
ft.dropdown.Option("크롬"),
|
||||
ft.dropdown.Option("웨일")
|
||||
],
|
||||
value="크롬"
|
||||
)
|
||||
|
||||
db_input_button = ft.ElevatedButton("DB 입력", on_click=lambda e: log("DB 입력 버튼 클릭 - 엑셀 파일 선택 기능 구현 필요"))
|
||||
view_data_button = ft.ElevatedButton("데이터 보기", on_click=lambda e: log("데이터 보기 버튼 클릭 - DB 테이블 표시 기능 구현 필요"))
|
||||
chrome_path_button = ft.ElevatedButton("크롬 경로 설정", on_click=lambda e: log("크롬 경로 설정 버튼 클릭 - 파일 선택 구현 필요"))
|
||||
whale_path_button = ft.ElevatedButton("웨일 경로 설정", on_click=lambda e: log("웨일 경로 설정 버튼 클릭 - 파일 선택 구현 필요"))
|
||||
run_button = ft.ElevatedButton("실행")
|
||||
|
||||
# --- 실행 버튼 이벤트 (run_task) ---
|
||||
def run_task(e):
|
||||
country = country_dropdown.value
|
||||
grade = grade_dropdown.value
|
||||
try:
|
||||
count = int(count_field.value)
|
||||
except:
|
||||
count = 1000
|
||||
remove_existing = remove_existing_checkbox.value
|
||||
extract_based = extract_based_checkbox.value
|
||||
try:
|
||||
max_extract = int(max_extract_field.value)
|
||||
except:
|
||||
max_extract = 1
|
||||
|
||||
# DB에서 조건에 맞는 데이터 추출 (간략화된 예제)
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
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 < ? ORDER BY extract_count ASC, RANDOM()"
|
||||
query += f" LIMIT {count}"
|
||||
df = pd.read_sql_query(query, conn, params=(max_extract,))
|
||||
else:
|
||||
query += " ORDER BY RANDOM()"
|
||||
query += f" LIMIT {count}"
|
||||
df = pd.read_sql_query(query, conn)
|
||||
conn.close()
|
||||
if df.empty:
|
||||
log("추출 가능한 데이터가 없습니다.")
|
||||
return
|
||||
nonlocal bookmarks
|
||||
bookmarks = df.to_dict("records")
|
||||
log(f"{len(bookmarks)}개의 북마크를 추출했습니다.")
|
||||
except Exception as ex:
|
||||
log(f"DB 쿼리 실행 중 오류 발생: {str(ex)}")
|
||||
return
|
||||
|
||||
folder_name = f"거상북마크-{grade}"
|
||||
if browser_dropdown.value == "크롬":
|
||||
selected_bookmarks_path = chrome_bookmarks_path
|
||||
selected_browser_path = chrome_path
|
||||
selected_browser = "크롬"
|
||||
log("크롬 브라우저가 선택되었습니다.")
|
||||
else:
|
||||
selected_bookmarks_path = whale_bookmarks_path
|
||||
selected_browser_path = whale_path
|
||||
selected_browser = "웨일"
|
||||
log("웨일 브라우저가 선택되었습니다.")
|
||||
|
||||
if not os.path.exists(selected_browser_path):
|
||||
log("선택된 브라우저 실행 파일 경로가 유효하지 않습니다.")
|
||||
return
|
||||
if not os.path.exists(selected_bookmarks_path):
|
||||
log("선택된 브라우저의 북마크 경로가 유효하지 않습니다.")
|
||||
return
|
||||
|
||||
worker = BookmarkWorker(
|
||||
bookmarks, folder_name, selected_bookmarks_path,
|
||||
selected_browser_path, selected_browser, remove_existing,
|
||||
update_progress, log, task_completed
|
||||
)
|
||||
worker.start()
|
||||
|
||||
run_button.on_click = run_task
|
||||
|
||||
# --- 레이아웃 구성 ---
|
||||
controls_row = ft.Row(controls=[
|
||||
country_dropdown,
|
||||
grade_dropdown,
|
||||
count_field,
|
||||
remove_existing_checkbox
|
||||
])
|
||||
extract_row = ft.Row(controls=[extract_based_checkbox, max_extract_field])
|
||||
browser_row = ft.Row(controls=[browser_dropdown, chrome_path_button, whale_path_button])
|
||||
action_row = ft.Row(controls=[db_input_button, view_data_button, run_button])
|
||||
|
||||
page.add(
|
||||
controls_row,
|
||||
extract_row,
|
||||
browser_row,
|
||||
action_row,
|
||||
progress_bar,
|
||||
ft.Text("로그:", size=14),
|
||||
log_display
|
||||
)
|
||||
|
||||
page.update()
|
||||
|
||||
ft.app(target=main)
|
||||
Loading…
Reference in New Issue