5
5
from tkinter import messagebox
6
6
import logging
7
7
import subprocess
8
- import atexit
8
+ # import atexit
9
9
from logger import logger
10
10
11
11
def is_compiled ():
@@ -20,29 +20,106 @@ def get_executable_path():
20
20
21
21
def get_target_dir ():
22
22
"""获取推荐安装目录"""
23
- appdata = os .getenv ('APPDATA' )
24
- return os .path .join (appdata , 'CourseScheduler' )
23
+ return os .path .join (os .environ ['APPDATA' ], 'CourseScheduler' )
25
24
26
25
def is_desktop_path (path ):
27
26
"""检查是否在桌面目录"""
28
27
desktop = os .path .expanduser ('~/Desktop' )
29
28
return os .path .normpath (path ).lower () == os .path .normpath (desktop ).lower ()
30
29
31
30
def create_shortcut (target , shortcut_path ):
32
- """创建快捷方式(需要pywin32) """
31
+ """使用VBScript创建快捷方式 """
33
32
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
+
40
87
except Exception as e :
41
88
logger .log_error (f"快捷方式创建失败: { str (e )} " )
42
89
messagebox .showerror ("错误" , "无法创建快捷方式,请手动创建" )
43
90
44
91
def check_installation ():
45
92
"""主安装检查逻辑"""
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
+
46
123
if not is_compiled ():
47
124
return # 开发模式不处理
48
125
@@ -70,6 +147,27 @@ def check_installation():
70
147
if not choice :
71
148
return
72
149
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
+
73
171
# 开始安装流程
74
172
target_dir = get_target_dir ()
75
173
exe_name = os .path .basename (get_executable_path ())
@@ -79,33 +177,118 @@ def check_installation():
79
177
# 创建目标目录
80
178
os .makedirs (target_dir , exist_ok = True )
81
179
82
- # 复制文件
180
+ # 创建目标目录结构
181
+ os .makedirs (os .path .join (target_dir , "res" ), exist_ok = True )
182
+
183
+ # 复制程序文件和资源文件
83
184
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
+
84
194
logger .log_info (f"程序已复制到: { target_path } " )
85
195
86
196
# 创建快捷方式
87
197
desktop = os .path .expanduser ('~/Desktop' )
88
- shortcut_path = os .path .join (desktop , "桌面课程表 .lnk" )
198
+ shortcut_path = os .path .join (desktop , "桌面课表 .lnk" )
89
199
create_shortcut (target_path , shortcut_path )
90
200
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 )
109
292
110
293
except Exception as e :
111
294
logger .log_error (f"安装失败: { str (e )} " )
0 commit comments