UtilityLibAPI 1.2510.23__tar.gz → 1.2511.28__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: UtilityLibAPI
3
- Version: 1.2510.23
3
+ Version: 1.2511.28
4
4
  Summary: UtilityLibAPI Python package
5
5
  Author-email: James Lin <tylin123@ms27.hinet.net>
6
6
  License: Copyright (c) 2025 James Lin **UtilityLibAPI**
@@ -33,10 +33,15 @@ Dynamic: license-file
33
33
  #UtilityLibAPI Classes
34
34
 
35
35
  ## History of version
36
+ Version 1.2511.28: 2025/11/27<BR>
37
+ Fixed 7zArchiveLib --> 7z Archive tools library
38
+
39
+ Version 1.2511.27: 2025/11/27<BR>
40
+ Add 7zArchiveLib --> 7z Archive tools library
41
+
36
42
  Version 1.2510.22: 2025/10/21<BR>
37
43
  Add ExtWrapper7zLib --> 7z Archive tools library
38
44
 
39
-
40
45
  Version 1.2510.22: 2025/10/21<BR>
41
46
  Add MailSenderLib --> CLASS_MailSender library
42
47
 
@@ -1,10 +1,15 @@
1
1
  #UtilityLibAPI Classes
2
2
 
3
3
  ## History of version
4
+ Version 1.2511.28: 2025/11/27<BR>
5
+ Fixed 7zArchiveLib --> 7z Archive tools library
6
+
7
+ Version 1.2511.27: 2025/11/27<BR>
8
+ Add 7zArchiveLib --> 7z Archive tools library
9
+
4
10
  Version 1.2510.22: 2025/10/21<BR>
5
11
  Add ExtWrapper7zLib --> 7z Archive tools library
6
12
 
7
-
8
13
  Version 1.2510.22: 2025/10/21<BR>
9
14
  Add MailSenderLib --> CLASS_MailSender library
10
15
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "UtilityLibAPI"
7
- version = "1.2510.23"
7
+ version = "1.2511.28"
8
8
  description = "UtilityLibAPI Python package"
9
9
  authors = [
10
10
  { name="James Lin", email="tylin123@ms27.hinet.net" }
@@ -0,0 +1,335 @@
1
+ import os
2
+ import sys
3
+ import shutil
4
+ import subprocess
5
+ import re
6
+ import glob
7
+
8
+ class CLASS_7zArchive:
9
+ def __init__(self):
10
+ self.on_progress = None
11
+ self.seven_zip_path = self._find_7z_exe()
12
+
13
+ # -------------------------------------------------------------
14
+ # 自動搜尋 7z.exe
15
+ # -------------------------------------------------------------
16
+ def _find_7z_exe(self):
17
+ candidate_paths = [
18
+ r"C:\Program Files\7-Zip\7z.exe",
19
+ r"C:\Program Files (x86)\7-Zip\7z.exe"
20
+ ]
21
+ # 1. 先找常用路徑
22
+ for p in candidate_paths:
23
+ if os.path.exists(p):
24
+ return p
25
+ # 2. 從 PATH 搜尋
26
+ p = shutil.which("7z")
27
+ if p:
28
+ return p
29
+
30
+ # 3. 找不到 → 拋錯
31
+ raise FileNotFoundError("找不到 7z.exe,請確認是否已安裝 7-Zip")
32
+
33
+
34
+ # -------------------------------------------------------------
35
+ # 設定進度事件
36
+ # -------------------------------------------------------------
37
+ def CUF_SET_ProgressEvent(self, func_progress):
38
+ self.on_progress = func_progress
39
+
40
+
41
+ def _find_7z_exe(self):
42
+ """
43
+ 依照優先順序尋找 7za.exe → 7z.exe
44
+ 並支援掃描所有磁碟(C, D, E, F...)
45
+ """
46
+ # ---------------------------
47
+ # 1. PATH 中找 7za / 7z
48
+ # ---------------------------
49
+ p = shutil.which("7za")
50
+ if p:
51
+ return p
52
+ p = shutil.which("7z")
53
+ if p:
54
+ return p
55
+ # ---------------------------
56
+ # 2. 常見安裝路徑
57
+ # ---------------------------
58
+ common_paths = [
59
+ r"C:\Program Files\7-Zip\7za.exe",
60
+ r"C:\Program Files (x86)\7-Zip\7za.exe",
61
+ r"C:\Program Files\7-Zip\7z.exe",
62
+ r"C:\Program Files (x86)\7-Zip\7z.exe",
63
+ ]
64
+ for p in common_paths:
65
+ if os.path.exists(p):
66
+ return p
67
+ # ---------------------------
68
+ # 3. 掃描所有磁碟(C, D, E, F...)
69
+ # 搜尋特定資料夾名稱避免太慢
70
+ # ---------------------------
71
+ search_dirs = [
72
+ "7-Zip",
73
+ "7Zip",
74
+ "7zip",
75
+ "7zip\\bin",
76
+ "Program Files\\7-Zip",
77
+ "Program Files (x86)\\7-Zip",
78
+ ]
79
+ drives = []
80
+ for letter in "CDEFGHIJKLMNOPQRSTUVWXYZ":
81
+ d = f"{letter}:\\"
82
+ if os.path.exists(d):
83
+ drives.append(d)
84
+
85
+ for drive in drives:
86
+ for folder in search_dirs:
87
+ base = os.path.join(drive, folder)
88
+
89
+ # 若資料夾不存在跳過
90
+ if not os.path.isdir(base):
91
+ continue
92
+
93
+ # 檢查底下是否有 7za.exe 或 7z.exe
94
+ exe_7za = os.path.join(base, "7za.exe")
95
+ exe_7z = os.path.join(base, "7z.exe")
96
+
97
+ if os.path.exists(exe_7za):
98
+ return exe_7za
99
+ if os.path.exists(exe_7z):
100
+ return exe_7z
101
+
102
+ # 若還沒找到 → 試著遞迴搜尋
103
+ try:
104
+ for root, dirs, files in os.walk(base):
105
+ if "7za.exe" in files:
106
+ return os.path.join(root, "7za.exe")
107
+ if "7z.exe" in files:
108
+ return os.path.join(root, "7z.exe")
109
+ except PermissionError:
110
+ pass # 跳過無權限的資料夾
111
+
112
+ # ---------------------------
113
+ # 全部找不到 → 拋錯
114
+ # ---------------------------
115
+ raise FileNotFoundError("找不到 7za.exe 或 7z.exe,請確認是否已安裝 7-Zip")
116
+
117
+ # -------------------------------------------------------------
118
+ # 封裝執行 7z.exe,並解析 stdout 進度 (%)
119
+ # -------------------------------------------------------------
120
+ def _run_7z(self, args_list):
121
+ """
122
+ 同時支援:
123
+ ✔ 有 % → 觸發 int 進度
124
+ ✔ 沒 % → 逐行觸發文字行
125
+ """
126
+ cmd = [self.seven_zip_path] + args_list
127
+
128
+ process = subprocess.Popen(
129
+ cmd,
130
+ stdout=subprocess.PIPE,
131
+ stderr=subprocess.STDOUT,
132
+ universal_newlines=True,
133
+ encoding="utf-8",
134
+ errors="ignore"
135
+ )
136
+
137
+ progress_pattern = re.compile(r"(\d+)%")
138
+
139
+ for line in process.stdout:
140
+ line = line.strip()
141
+
142
+ # 嘗試找百分比
143
+ match = progress_pattern.search(line)
144
+ if match:
145
+ pct = int(match.group(1))
146
+ if self.on_progress:
147
+ try:
148
+ self.on_progress(pct)
149
+ except:
150
+ pass
151
+ else:
152
+ # 沒有百分比 → 仍然要回報文字行(使用者可自訂怎麼處理)
153
+ if self.on_progress and line:
154
+ try:
155
+ self.on_progress(line) # 回傳文字行
156
+ except:
157
+ pass
158
+ process.wait()
159
+
160
+
161
+ # -------------------------------------------------------------
162
+ # 壓縮資料夾
163
+ # -------------------------------------------------------------
164
+ def CUF_CompressFolder(self, Pms_FolderName:str, Pms_File7z:str,
165
+ Pms_Password:str=None,
166
+ Pmi_SplitSize_K:int=-1):
167
+
168
+ if not os.path.exists(Pms_FolderName):
169
+ raise ValueError("Pms_FolderName 必須是資料夾")
170
+
171
+ # -------------------------------------------------------
172
+ # 取出 Pms_File7z 的路徑並建立
173
+ # -------------------------------------------------------
174
+ out_dir = os.path.dirname(Pms_File7z)
175
+ if out_dir.strip() == "":
176
+ out_dir = os.getcwd()
177
+
178
+ os.makedirs(out_dir, exist_ok=True)
179
+
180
+ file_name_only = os.path.basename(Pms_File7z) # Orange.7z
181
+
182
+ # -------------------------------------------------------
183
+ # 組合 7z 參數
184
+ # -------------------------------------------------------
185
+ args = ["a"]
186
+
187
+ if Pms_Password:
188
+ args.append(f"-p{Pms_Password}")
189
+
190
+ if Pmi_SplitSize_K != -1:
191
+ args.append(f"-v{Pmi_SplitSize_K}k")
192
+
193
+ # 7z 輸出檔名(不能帶路徑)
194
+ args.append(file_name_only)
195
+
196
+ # 加入來源資料夾
197
+ args.append(Pms_FolderName)
198
+
199
+ # -------------------------------------------------------
200
+ # 執行壓縮 (7z 產生在目前工作目錄)
201
+ # -------------------------------------------------------
202
+ self._run_7z(args)
203
+
204
+ # -------------------------------------------------------
205
+ # ▒▒▒ 壓縮後 → 將檔案移到 Pms_File7z 指定路徑 ▒▒▒
206
+ # -------------------------------------------------------
207
+ self._move_output_files(file_name_only, out_dir)
208
+
209
+
210
+ # -------------------------------------------------------------
211
+ # 壓縮檔案(支援萬用字元 *.txt, apple*.*)
212
+ # -------------------------------------------------------------
213
+ def CUF_CompressFile(self, Pms_FileSource:str, Pms_File7z:str,
214
+ Pms_Password:str=None,
215
+ Pmi_SplitSize_K:int=-1):
216
+ file_list = glob.glob(Pms_FileSource)
217
+ if not file_list:
218
+ raise FileNotFoundError(f"找不到符合的檔案: {Pms_FileSource}")
219
+
220
+ # -------------------------------------------------------
221
+ # 取出 Pms_File7z 的路徑並建立
222
+ # -------------------------------------------------------
223
+ out_dir = os.path.dirname(Pms_File7z)
224
+ if out_dir.strip() == "":
225
+ out_dir = os.getcwd()
226
+
227
+ os.makedirs(out_dir, exist_ok=True)
228
+
229
+ file_name_only = os.path.basename(Pms_File7z)
230
+
231
+ # -------------------------------------------------------
232
+ # 組合 7z 參數
233
+ # -------------------------------------------------------
234
+ args = ["a"]
235
+
236
+ if Pms_Password:
237
+ args.append(f"-p{Pms_Password}")
238
+
239
+ if Pmi_SplitSize_K not in (None, -1):
240
+ args.append(f"-v{Pmi_SplitSize_K}k")
241
+
242
+ args.append(file_name_only)
243
+
244
+ for f in file_list:
245
+ args.append(f)
246
+
247
+ # -------------------------------------------------------
248
+ # 執行壓縮
249
+ # -------------------------------------------------------
250
+ self._run_7z(args)
251
+
252
+ # -------------------------------------------------------
253
+ # ▒▒▒ 壓縮後 → 移動所有分割檔 / 單一檔 ▒▒▒
254
+ # -------------------------------------------------------
255
+ self._move_output_files(file_name_only, out_dir)
256
+
257
+
258
+ # -------------------------------------------------------------
259
+ # 共用:移動壓縮結果到指定目錄
260
+ # -------------------------------------------------------------
261
+ def _move_output_files(self, base_file_name:str, out_dir:str):
262
+ """
263
+ base_file_name = "Orange.7z"
264
+ 最後尋找:
265
+ Orange.7z
266
+ Orange.7z.001
267
+ Orange.7z.002
268
+ ...
269
+ """
270
+ for f in os.listdir("."):
271
+ if f == base_file_name or f.startswith(base_file_name + "."):
272
+ shutil.move(f, os.path.join(out_dir, f))
273
+
274
+
275
+ def CUF_Decompress(self, Pms_ArchiveFile, Pms_OutputDir, Pms_Password=None):
276
+ """
277
+ 解壓縮通用函式(最終穩定版本)
278
+ -------------------------------------
279
+ Pms_ArchiveFile : 壓縮檔完整路徑
280
+ Pms_OutputDir : 要解壓到哪裡
281
+ Pms_Password : 解壓密碼(可省略)
282
+ -------------------------------------
283
+ 支援格式:
284
+ 7z, zip, rar, tar, tar.gz, tar.bz2, tar.xz
285
+ """
286
+ # ---------- 路徑檢查 ----------
287
+ if not os.path.exists(Pms_ArchiveFile):
288
+ return False, f"Compress file not found: {Pms_ArchiveFile}"
289
+
290
+ os.makedirs(Pms_OutputDir, exist_ok=True)
291
+
292
+ Pms_7zExe = self.seven_zip_path
293
+
294
+ args = ["x"]
295
+
296
+ if Pms_Password:
297
+ args.append(f"-p{Pms_Password}")
298
+
299
+ args.append(Pms_ArchiveFile)
300
+ args.append(f"-o{Pms_OutputDir}")
301
+ args.append("-y")
302
+
303
+ # ★★★ 使用會有進度事件的版本 ★★★
304
+ try:
305
+ self._run_7z(args)
306
+ return True, "Decompress successful."
307
+ except Exception as e:
308
+ return False, f"Decompress failure:\n{str(e)}"
309
+
310
+
311
+ # ================================================================================
312
+ # [使用範例]
313
+ def GE_ProgressEvent(Pmf_Percent):
314
+ print(str(Pmf_Percent));
315
+
316
+ if(__name__=="__main__"):
317
+ ms_Path_7z: str;
318
+
319
+ obj_7z = CLASS_7zArchive();
320
+
321
+ obj_7z.CUF_SET_ProgressEvent(GE_ProgressEvent);
322
+
323
+ obj_7z.CUF_CompressFolder(Pms_FolderName="X:\\TEMP\\BOOK\\MUSIC",
324
+ Pms_File7z="X:\\temp\\book\\PPPK\\GOOD_FILE",
325
+ Pms_Password="Gotech123",
326
+ Pmi_SplitSize_K=640);
327
+
328
+ obj_7z.CUF_CompressFile(Pms_FileSource="X:\\temp\\book\\*.png",
329
+ Pms_File7z="X:\\temp\\book\\abcde\\pqrst\\PPPK-2\\我的PNG圖檔",
330
+ Pms_Password="OKOKGOOD",
331
+ Pmi_SplitSize_K=100);
332
+
333
+ obj_7z.CUF_Decompress(Pms_ArchiveFile="X:\\TEMP\\BOOK\\abcde\\pqrst\\PPPK-2\\我的PNG圖檔.7z.001",
334
+ Pms_OutputDir="X:\\temp\\OK了",
335
+ Pms_Password="OKOKGOOD")
@@ -2,9 +2,11 @@ from .EnDeCodeLib import CLASS_EnDeCodeLib
2
2
  from .SessionVARLib import CLASS_SessionVAR
3
3
  from .MailSenderLib import CLASS_MailSender
4
4
  from .ExtWrapper7zLib import CLASS_ExtWrapper7z
5
+ from .7zArchiveLib import CLASS_7zArchive
5
6
 
6
7
  __all__ = ["CLASS_EnDeCodeLib",
7
8
  "CLASS_SessionVAR",
8
9
  "CLASS_MailSender",
9
- "CLASS_ExtWrapper7z"
10
+ "CLASS_ExtWrapper7z",
11
+ "CLASS_7zArchive"
10
12
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: UtilityLibAPI
3
- Version: 1.2510.23
3
+ Version: 1.2511.28
4
4
  Summary: UtilityLibAPI Python package
5
5
  Author-email: James Lin <tylin123@ms27.hinet.net>
6
6
  License: Copyright (c) 2025 James Lin **UtilityLibAPI**
@@ -33,10 +33,15 @@ Dynamic: license-file
33
33
  #UtilityLibAPI Classes
34
34
 
35
35
  ## History of version
36
+ Version 1.2511.28: 2025/11/27<BR>
37
+ Fixed 7zArchiveLib --> 7z Archive tools library
38
+
39
+ Version 1.2511.27: 2025/11/27<BR>
40
+ Add 7zArchiveLib --> 7z Archive tools library
41
+
36
42
  Version 1.2510.22: 2025/10/21<BR>
37
43
  Add ExtWrapper7zLib --> 7z Archive tools library
38
44
 
39
-
40
45
  Version 1.2510.22: 2025/10/21<BR>
41
46
  Add MailSenderLib --> CLASS_MailSender library
42
47
 
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  setup.cfg
5
+ src/UtilityLibAPI/7zArchiveLib.py
5
6
  src/UtilityLibAPI/EnDeCodeLib.py
6
7
  src/UtilityLibAPI/ExtWrapper7zLib.py
7
8
  src/UtilityLibAPI/MailSenderLib.py