Skip to content

Commit b75e170

Browse files
committed
refactor(installer): 修复上个版本的半成品安装模块
- 移除对 pywin32 的依赖,使用 VBScript 创建快捷方式 - 添加管理员权限检查和相关错误处理 - 实现安装过程中的临时文件清理 - 重构安装以及启动方法
1 parent 94b613a commit b75e170

File tree

1 file changed

+213
-30
lines changed

1 file changed

+213
-30
lines changed

installer.py

Lines changed: 213 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from tkinter import messagebox
66
import logging
77
import subprocess
8-
import atexit
8+
#import atexit
99
from logger import logger
1010

1111
def is_compiled():
@@ -20,29 +20,106 @@ def get_executable_path():
2020

2121
def get_target_dir():
2222
"""获取推荐安装目录"""
23-
appdata = os.getenv('APPDATA')
24-
return os.path.join(appdata, 'CourseScheduler')
23+
return os.path.join(os.environ['APPDATA'], 'CourseScheduler')
2524

2625
def is_desktop_path(path):
2726
"""检查是否在桌面目录"""
2827
desktop = os.path.expanduser('~/Desktop')
2928
return os.path.normpath(path).lower() == os.path.normpath(desktop).lower()
3029

3130
def create_shortcut(target, shortcut_path):
32-
"""创建快捷方式(需要pywin32)"""
31+
"""使用VBScript创建快捷方式"""
3332
try:
34-
import win32com.client
35-
shell = win32com.client.Dispatch("WScript.Shell")
36-
shortcut = shell.CreateShortCut(shortcut_path)
37-
shortcut.Targetpath = target
38-
shortcut.WorkingDirectory = os.path.dirname(target)
39-
shortcut.save()
33+
# 图标路径指向AppData目录下的res/icon.ico
34+
target_dir = os.path.dirname(target)
35+
icon_path = os.path.join(os.environ['APPDATA'], 'CourseScheduler', "res", "icon.ico")
36+
37+
# 检查图标文件是否存在
38+
icon_location = ""
39+
if os.path.exists(icon_path):
40+
icon_location = f'oLink.IconLocation = "{icon_path}"\n '
41+
else:
42+
logger.log_warning(f"图标文件不存在: {icon_path}")
43+
44+
vbs_script = f"""
45+
Set oWS = WScript.CreateObject("WScript.Shell")
46+
Set oLink = oWS.CreateShortcut("{shortcut_path}")
47+
oLink.TargetPath = "{target}"
48+
oLink.WorkingDirectory = "{os.path.dirname(target)}"
49+
oLink.Description = "课程表快捷方式"
50+
{icon_location}oLink.Save
51+
"""
52+
53+
# 创建临时VBS文件
54+
temp_vbs = os.path.join(os.environ['TEMP'], 'create_shortcut.vbs')
55+
with open(temp_vbs, 'w', encoding='gbk') as f:
56+
f.write(vbs_script)
57+
58+
# 执行VBS脚本
59+
result = subprocess.run(
60+
['cscript.exe', temp_vbs],
61+
capture_output=True,
62+
text=True,
63+
shell=True
64+
)
65+
66+
if result.returncode != 0:
67+
raise RuntimeError(f"VBScript执行失败: {result.stderr}")
68+
69+
# 删除临时文件
70+
os.remove(temp_vbs)
71+
72+
except Exception as e:
73+
logger.log_error(f"快捷方式创建失败: {str(e)}")
74+
messagebox.showerror("错误", f"无法创建快捷方式: {str(e)}")
75+
76+
# 创建临时VBS文件
77+
temp_vbs = os.path.join(os.environ['TEMP'], 'create_shortcut.vbs')
78+
with open(temp_vbs, 'w', encoding='gbk') as f:
79+
f.write(vbs_script)
80+
81+
# 执行VBS脚本
82+
subprocess.run(['cscript.exe', temp_vbs], check=True, shell=True)
83+
84+
# 删除临时文件
85+
os.remove(temp_vbs)
86+
4087
except Exception as e:
4188
logger.log_error(f"快捷方式创建失败: {str(e)}")
4289
messagebox.showerror("错误", "无法创建快捷方式,请手动创建")
4390

4491
def check_installation():
4592
"""主安装检查逻辑"""
93+
# 程序启动时清理残留的计划任务
94+
def cleanup_scheduled_task():
95+
try:
96+
task_name = "CourseDelayedCleanup"
97+
bat_path = os.path.join(os.getcwd(), 'delayed_cleanup.bat')
98+
99+
# 删除临时批处理文件
100+
if os.path.exists(bat_path):
101+
os.remove(bat_path)
102+
logger.log_debug(f"已清理临时批处理文件: {bat_path}")
103+
104+
# 清理计划任务
105+
check_result = subprocess.run(
106+
f'schtasks /query /tn "{task_name}"',
107+
shell=True,
108+
capture_output=True,
109+
text=True
110+
)
111+
if check_result.returncode == 0:
112+
subprocess.run(
113+
f'schtasks /delete /tn "{task_name}" /f',
114+
shell=True,
115+
check=True
116+
)
117+
logger.log_debug(f"已清理计划任务: {task_name}")
118+
except Exception as e:
119+
logger.log_error(f"启动清理失败: {str(e)}")
120+
121+
cleanup_scheduled_task()
122+
46123
if not is_compiled():
47124
return # 开发模式不处理
48125

@@ -70,6 +147,27 @@ def check_installation():
70147
if not choice:
71148
return
72149

150+
# 检查管理员权限
151+
try:
152+
import ctypes
153+
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
154+
except:
155+
is_admin = False
156+
157+
if not is_admin:
158+
# 清理临时文件
159+
try:
160+
if os.path.exists('config.json'):
161+
os.remove('config.json')
162+
if os.path.exists('logs'):
163+
shutil.rmtree('logs')
164+
except Exception as e:
165+
logger.log_error(f"清理临时文件失败: {str(e)}")
166+
167+
messagebox.showerror("权限错误", "需要管理员权限才能完成安装")
168+
sys.exit(1)
169+
170+
73171
# 开始安装流程
74172
target_dir = get_target_dir()
75173
exe_name = os.path.basename(get_executable_path())
@@ -79,33 +177,118 @@ def check_installation():
79177
# 创建目标目录
80178
os.makedirs(target_dir, exist_ok=True)
81179

82-
# 复制文件
180+
# 创建目标目录结构
181+
os.makedirs(os.path.join(target_dir, "res"), exist_ok=True)
182+
183+
# 复制程序文件和资源文件
83184
shutil.copy2(get_executable_path(), target_path)
185+
186+
# 复制图标文件
187+
src_icon = os.path.join(os.path.dirname(get_executable_path()), "res", "icon.ico")
188+
if hasattr(sys, '_MEIPASS'):
189+
src_icon = os.path.join(sys._MEIPASS, "res", "icon.ico")
190+
191+
if os.path.exists(src_icon):
192+
shutil.copy2(src_icon, os.path.join(target_dir, "res", "icon.ico"))
193+
84194
logger.log_info(f"程序已复制到: {target_path}")
85195

86196
# 创建快捷方式
87197
desktop = os.path.expanduser('~/Desktop')
88-
shortcut_path = os.path.join(desktop, "桌面课程表.lnk")
198+
shortcut_path = os.path.join(desktop, "桌面课表.lnk")
89199
create_shortcut(target_path, shortcut_path)
90200

91-
# 启动新实例并删除旧文件
92-
subprocess.Popen([target_path])
93-
94-
# 注册退出时删除原文件
95-
def cleanup():
96-
try:
97-
# 删除原程序文件
98-
os.remove(get_executable_path())
99-
# 清理临时配置文件
100-
temp_dir = os.path.join(os.path.dirname(get_executable_path()), 'temp')
101-
if os.path.exists(temp_dir):
102-
shutil.rmtree(temp_dir)
103-
logger.log_info("原文件及临时目录已清理")
104-
except Exception as e:
105-
logger.log_error(f"清理失败: {str(e)}")
106-
107-
atexit.register(cleanup)
108-
sys.exit(0)
201+
# 创建批处理文件(启动新程序+清理桌面文件)
202+
bat_path = os.path.join(os.getcwd(), 'install_cleanup.bat')
203+
desktop_path = os.path.expanduser('~/Desktop')
204+
exe_path = get_executable_path()
205+
206+
script_content = f"""@echo off
207+
chcp 65001 >nul
208+
:: 以管理员权限启动新程序
209+
210+
:: 清理桌面文件(重试机制)
211+
:cleanup_loop
212+
del /f /q "{desktop_path}\\config.json" >nul 2>&1
213+
del /f /q "{desktop_path}\\schedule.json" >nul 2>&1
214+
if exist "{desktop_path}\\logs" (
215+
rmdir /s /q "{desktop_path}\\logs" >nul 2>&1
216+
)
217+
218+
:: 检查是否清理完成
219+
if exist "{desktop_path}\\config.json" (
220+
timeout /t 1 /nobreak >nul
221+
goto cleanup_loop
222+
)
223+
if exist "{desktop_path}\\schedule.json" (
224+
timeout /t 1 /nobreak >nul
225+
goto cleanup_loop
226+
)
227+
if exist "{desktop_path}\\logs" (
228+
timeout /t 1 /nobreak >nul
229+
goto cleanup_loop
230+
)
231+
232+
:: 直接启动程序并设置工作目录
233+
start "" /D "%APPDATA%\CourseScheduler" "{target_path}"
234+
235+
:: 删除批处理脚本自身
236+
del /f /q "%~f0" >nul 2>&1
237+
exit
238+
"""
239+
with open(bat_path, 'w', encoding='gbk') as f:
240+
f.write(script_content)
241+
242+
# 创建并执行计划任务
243+
task_name = "CourseInstallerTask"
244+
try:
245+
# 检查批处理文件是否存在
246+
if not os.path.exists(bat_path):
247+
raise FileNotFoundError(f"批处理文件不存在: {bat_path}")
248+
249+
# 创建计划任务(带详细日志)
250+
create_cmd = f'schtasks /create /tn "{task_name}" /tr "{bat_path}" /sc once /st 00:00 /f'
251+
logger.log_debug(f"执行命令: {create_cmd}")
252+
253+
create_result = subprocess.run(
254+
create_cmd,
255+
shell=True,
256+
capture_output=True,
257+
text=True
258+
)
259+
260+
if create_result.returncode != 0:
261+
error_msg = f"计划任务创建失败(代码{create_result.returncode}): {create_result.stderr}"
262+
logger.log_error(error_msg)
263+
raise RuntimeError(error_msg)
264+
265+
# 立即运行计划任务
266+
run_cmd = f'schtasks /run /tn "{task_name}"'
267+
logger.log_debug(f"执行命令: {run_cmd}")
268+
269+
run_result = subprocess.run(
270+
run_cmd,
271+
shell=True,
272+
capture_output=True,
273+
text=True
274+
)
275+
276+
if run_result.returncode != 0:
277+
error_msg = f"计划任务启动失败(代码{run_result.returncode}): {run_result.stderr}"
278+
logger.log_error(error_msg)
279+
raise RuntimeError(error_msg)
280+
281+
logger.log_info("计划任务创建并执行成功")
282+
sys.exit(0)
283+
284+
except Exception as e:
285+
logger.log_error(f"安装过程中发生错误: {str(e)}")
286+
logger.log_debug(f"批处理文件路径: {bat_path}")
287+
logger.log_debug(f"目标程序路径: {target_path}")
288+
messagebox.showerror("安装错误",
289+
f"安装过程中发生错误:\n{str(e)}\n"
290+
f"请手动启动新程序: {target_path}")
291+
sys.exit(1)
109292

110293
except Exception as e:
111294
logger.log_error(f"安装失败: {str(e)}")

0 commit comments

Comments
 (0)