qrpa 1.0.34__py3-none-any.whl → 1.1.50__py3-none-any.whl
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.
- qrpa/RateLimitedSender.py +45 -45
- qrpa/__init__.py +31 -26
- qrpa/db_migrator.py +600 -600
- qrpa/feishu_bot_app.py +267 -267
- qrpa/feishu_client.py +410 -0
- qrpa/feishu_logic.py +1443 -0
- qrpa/fun_base.py +339 -337
- qrpa/fun_excel.py +529 -61
- qrpa/fun_file.py +318 -318
- qrpa/fun_web.py +258 -148
- qrpa/fun_win.py +198 -198
- qrpa/mysql_module/__init__.py +0 -0
- qrpa/mysql_module/new_product_analysis_model.py +556 -0
- qrpa/mysql_module/shein_ledger_model.py +468 -0
- qrpa/mysql_module/shein_product_model.py +495 -0
- qrpa/mysql_module/shein_return_order_model.py +569 -0
- qrpa/mysql_module/shein_store_model.py +595 -0
- qrpa/shein_daily_report_model.py +375 -375
- qrpa/shein_excel.py +1248 -109
- qrpa/shein_lib.py +2333 -143
- qrpa/shein_mysql.py +92 -0
- qrpa/shein_sqlite.py +153 -153
- qrpa/shein_ziniao.py +529 -450
- qrpa/temu_chrome.py +56 -56
- qrpa/temu_excel.py +139 -139
- qrpa/temu_lib.py +156 -154
- qrpa/time_utils.py +87 -50
- qrpa/time_utils_example.py +243 -243
- qrpa/wxwork.py +318 -318
- {qrpa-1.0.34.dist-info → qrpa-1.1.50.dist-info}/METADATA +1 -1
- qrpa-1.1.50.dist-info/RECORD +33 -0
- qrpa-1.0.34.dist-info/RECORD +0 -24
- {qrpa-1.0.34.dist-info → qrpa-1.1.50.dist-info}/WHEEL +0 -0
- {qrpa-1.0.34.dist-info → qrpa-1.1.50.dist-info}/top_level.txt +0 -0
qrpa/fun_file.py
CHANGED
|
@@ -1,319 +1,319 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import threading
|
|
3
|
-
import json
|
|
4
|
-
|
|
5
|
-
file_lock = threading.Lock() # 线程锁
|
|
6
|
-
|
|
7
|
-
from datetime import date, datetime, timedelta, timezone
|
|
8
|
-
|
|
9
|
-
from .fun_base import log
|
|
10
|
-
|
|
11
|
-
import os
|
|
12
|
-
import gc
|
|
13
|
-
import time
|
|
14
|
-
import psutil
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
def delete_file(file_path):
|
|
18
|
-
"""
|
|
19
|
-
删除文件的优化函数,自动处理已打开的文件
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
file_path (str): 要删除的文件路径
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
bool: 删除成功返回True,失败返回False
|
|
26
|
-
"""
|
|
27
|
-
try:
|
|
28
|
-
file_path = Path(file_path).resolve() # 规范化路径
|
|
29
|
-
|
|
30
|
-
if not file_path.exists():
|
|
31
|
-
log(f"文件 {file_path} 不存在。")
|
|
32
|
-
return False
|
|
33
|
-
|
|
34
|
-
# 强制垃圾回收,关闭可能的文件句柄
|
|
35
|
-
gc.collect()
|
|
36
|
-
|
|
37
|
-
# 第一次尝试直接删除
|
|
38
|
-
try:
|
|
39
|
-
os.remove(file_path)
|
|
40
|
-
log(f"文件 {file_path} 已成功删除。")
|
|
41
|
-
return True
|
|
42
|
-
except PermissionError:
|
|
43
|
-
log(f"文件 {file_path} 可能被占用,尝试关闭相关进程...")
|
|
44
|
-
|
|
45
|
-
# 尝试找到并关闭占用该文件的进程
|
|
46
|
-
if close_file_handles(file_path):
|
|
47
|
-
# 等待一小段时间让系统释放句柄
|
|
48
|
-
time.sleep(0.1)
|
|
49
|
-
|
|
50
|
-
# 再次尝试删除
|
|
51
|
-
try:
|
|
52
|
-
os.remove(file_path)
|
|
53
|
-
log(f"文件 {file_path} 已成功删除。")
|
|
54
|
-
return True
|
|
55
|
-
except PermissionError:
|
|
56
|
-
log(f"错误:即使尝试关闭文件句柄后,仍无法删除文件 {file_path}。")
|
|
57
|
-
return False
|
|
58
|
-
else:
|
|
59
|
-
log(f"错误:无法关闭文件 {file_path} 的句柄,删除失败。")
|
|
60
|
-
return False
|
|
61
|
-
|
|
62
|
-
except FileNotFoundError:
|
|
63
|
-
log(f"错误:文件 {file_path} 未找到。")
|
|
64
|
-
return False
|
|
65
|
-
except Exception as e:
|
|
66
|
-
log(f"错误:删除文件时发生未知错误:{e}")
|
|
67
|
-
return False
|
|
68
|
-
|
|
69
|
-
def close_file_handles(file_path):
|
|
70
|
-
"""
|
|
71
|
-
尝试关闭指定文件的所有句柄
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
file_path (Path): 文件路径
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
bool: 成功关闭返回True,失败返回False
|
|
78
|
-
"""
|
|
79
|
-
try:
|
|
80
|
-
file_path_str = str(file_path)
|
|
81
|
-
closed_any = False
|
|
82
|
-
|
|
83
|
-
# 遍历所有进程,查找打开该文件的进程
|
|
84
|
-
for proc in psutil.process_iter(['pid', 'name']):
|
|
85
|
-
try:
|
|
86
|
-
# 获取进程打开的文件列表
|
|
87
|
-
open_files = proc.open_files()
|
|
88
|
-
for open_file in open_files:
|
|
89
|
-
if os.path.samefile(open_file.path, file_path_str):
|
|
90
|
-
log(f"发现进程 {proc.info['name']} (PID: {proc.info['pid']}) 正在使用文件")
|
|
91
|
-
|
|
92
|
-
# 如果是当前Python进程,尝试强制关闭文件句柄
|
|
93
|
-
if proc.info['pid'] == os.getpid():
|
|
94
|
-
closed_any = True
|
|
95
|
-
else:
|
|
96
|
-
# 对于其他进程,可以选择终止(谨慎使用)
|
|
97
|
-
log(f"警告:文件被其他进程占用,请手动关闭应用程序")
|
|
98
|
-
|
|
99
|
-
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
100
|
-
continue
|
|
101
|
-
except Exception as e:
|
|
102
|
-
continue
|
|
103
|
-
|
|
104
|
-
return closed_any
|
|
105
|
-
|
|
106
|
-
except Exception as e:
|
|
107
|
-
log(f"关闭文件句柄时出错:{e}")
|
|
108
|
-
return False
|
|
109
|
-
|
|
110
|
-
def delete_file_simple(file_path):
|
|
111
|
-
"""
|
|
112
|
-
简化版本的删除文件函数(不需要额外依赖)
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
file_path (str): 要删除的文件路径
|
|
116
|
-
|
|
117
|
-
Returns:
|
|
118
|
-
bool: 删除成功返回True,失败返回False
|
|
119
|
-
"""
|
|
120
|
-
try:
|
|
121
|
-
file_path = os.path.abspath(file_path) # 获取绝对路径
|
|
122
|
-
|
|
123
|
-
if not os.path.exists(file_path):
|
|
124
|
-
log(f"文件 {file_path} 不存在。")
|
|
125
|
-
return False
|
|
126
|
-
|
|
127
|
-
# 强制垃圾回收
|
|
128
|
-
gc.collect()
|
|
129
|
-
|
|
130
|
-
# 尝试删除
|
|
131
|
-
try:
|
|
132
|
-
os.remove(file_path)
|
|
133
|
-
log(f"文件 {file_path} 已成功删除。")
|
|
134
|
-
return True
|
|
135
|
-
except PermissionError:
|
|
136
|
-
# 在Windows上,尝试修改文件权限
|
|
137
|
-
if os.name == 'nt': # Windows
|
|
138
|
-
try:
|
|
139
|
-
os.chmod(file_path, 0o777)
|
|
140
|
-
time.sleep(0.1) # 短暂等待
|
|
141
|
-
os.remove(file_path)
|
|
142
|
-
log(f"文件 {file_path} 已成功删除。")
|
|
143
|
-
return True
|
|
144
|
-
except Exception:
|
|
145
|
-
pass
|
|
146
|
-
|
|
147
|
-
log(f"错误:没有权限删除文件 {file_path},可能文件正在被使用。")
|
|
148
|
-
return False
|
|
149
|
-
|
|
150
|
-
except FileNotFoundError:
|
|
151
|
-
log(f"错误:文件 {file_path} 未找到。")
|
|
152
|
-
return False
|
|
153
|
-
except Exception as e:
|
|
154
|
-
log(f"错误:删除文件时发生未知错误:{e}")
|
|
155
|
-
return False
|
|
156
|
-
|
|
157
|
-
def read_dict_from_file(file_path, cache_interval=3600 * 24 * 365 * 10):
|
|
158
|
-
"""
|
|
159
|
-
从文件中读取字典。
|
|
160
|
-
如果文件的修改时间未超过一个小时,则返回字典;否则返回 None。
|
|
161
|
-
|
|
162
|
-
:param file_path: 文件路径
|
|
163
|
-
:return: 字典或 None
|
|
164
|
-
"""
|
|
165
|
-
with file_lock: # 使用锁保护文件操作
|
|
166
|
-
# 检查文件是否存在
|
|
167
|
-
if not os.path.exists(file_path):
|
|
168
|
-
return {}
|
|
169
|
-
|
|
170
|
-
# 获取文件的最后修改时间
|
|
171
|
-
modification_time = os.path.getmtime(file_path)
|
|
172
|
-
modification_time = datetime.fromtimestamp(modification_time)
|
|
173
|
-
|
|
174
|
-
# 获取当前时间
|
|
175
|
-
current_time = datetime.now()
|
|
176
|
-
|
|
177
|
-
interval = current_time - modification_time
|
|
178
|
-
log(f'缓存文件 {file_path} 缓存时长 {timedelta(seconds=int(cache_interval))} 已过时长 {interval}')
|
|
179
|
-
|
|
180
|
-
# 判断文件的修改时间是否超过一个小时
|
|
181
|
-
if interval <= timedelta(seconds=int(cache_interval)):
|
|
182
|
-
# 如果未超过一个小时,则读取文件内容
|
|
183
|
-
with open(file_path, "r", encoding='utf-8') as file:
|
|
184
|
-
return json.load(file)
|
|
185
|
-
else:
|
|
186
|
-
# 如果超过一个小时,则返回 None
|
|
187
|
-
return {}
|
|
188
|
-
|
|
189
|
-
def write_dict_to_file(file_path, data):
|
|
190
|
-
"""
|
|
191
|
-
将字典写入文件。
|
|
192
|
-
|
|
193
|
-
:param file_path: 文件路径
|
|
194
|
-
:param data: 要写入的字典
|
|
195
|
-
"""
|
|
196
|
-
with file_lock: # 使用锁保护文件操作
|
|
197
|
-
# 确保目标文件夹存在
|
|
198
|
-
dir_name = os.path.dirname(file_path)
|
|
199
|
-
if dir_name and not os.path.exists(dir_name):
|
|
200
|
-
os.makedirs(dir_name, exist_ok=True) # 递归创建目录
|
|
201
|
-
|
|
202
|
-
with open(file_path, 'w', encoding='utf-8') as f:
|
|
203
|
-
# 使用 json.dump() 并设置 ensure_ascii=False
|
|
204
|
-
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
205
|
-
|
|
206
|
-
def read_dict_from_file_ex(file_path, key, cache_interval=3600 * 24 * 365 * 10, default='dict'):
|
|
207
|
-
"""
|
|
208
|
-
从 JSON 文件中读取指定键的值。
|
|
209
|
-
|
|
210
|
-
:param file_path: JSON 文件路径
|
|
211
|
-
:param key: 要读取的键
|
|
212
|
-
:param default: 如果文件不存在、解析失败或键不存在时返回的默认值
|
|
213
|
-
:return: 对应键的值,或 default
|
|
214
|
-
"""
|
|
215
|
-
with file_lock: # 使用锁保护文件操作
|
|
216
|
-
if not os.path.exists(file_path):
|
|
217
|
-
return {} if default == 'dict' else []
|
|
218
|
-
|
|
219
|
-
# 获取文件的最后修改时间
|
|
220
|
-
modification_time = os.path.getmtime(file_path)
|
|
221
|
-
modification_time = datetime.fromtimestamp(modification_time)
|
|
222
|
-
|
|
223
|
-
# 获取当前时间
|
|
224
|
-
current_time = datetime.now()
|
|
225
|
-
|
|
226
|
-
interval = current_time - modification_time
|
|
227
|
-
log(f'缓存文件 {file_path} 缓存时长 {timedelta(seconds=cache_interval)} 已过时长 {interval}')
|
|
228
|
-
|
|
229
|
-
# 判断文件的修改时间是否超过一个小时
|
|
230
|
-
if interval <= timedelta(seconds=cache_interval):
|
|
231
|
-
# 如果未超过一个小时,则读取文件内容
|
|
232
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
233
|
-
data = json.load(f)
|
|
234
|
-
return data.get(key, {})
|
|
235
|
-
else:
|
|
236
|
-
# 如果超过一个小时,则返回 None
|
|
237
|
-
return {} if default == 'dict' else []
|
|
238
|
-
|
|
239
|
-
def write_dict_to_file_ex(file_path, data, update_keys=None):
|
|
240
|
-
"""
|
|
241
|
-
将字典写入文件,可选择性地只更新指定键。
|
|
242
|
-
|
|
243
|
-
:param file_path: 文件路径
|
|
244
|
-
:param data: 要写入的字典数据
|
|
245
|
-
:param update_keys: 可选,需要更新的键列表。如果为None,则替换整个文件内容
|
|
246
|
-
"""
|
|
247
|
-
with file_lock: # 使用锁保护文件操作
|
|
248
|
-
# 确保目标文件夹存在
|
|
249
|
-
dir_name = os.path.dirname(file_path)
|
|
250
|
-
if dir_name and not os.path.exists(dir_name):
|
|
251
|
-
os.makedirs(dir_name, exist_ok=True) # 递归创建目录
|
|
252
|
-
|
|
253
|
-
# 如果指定了update_keys,先读取现有数据然后合并
|
|
254
|
-
if update_keys is not None:
|
|
255
|
-
try:
|
|
256
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
257
|
-
existing_data = json.load(f)
|
|
258
|
-
except (FileNotFoundError, json.JSONDecodeError):
|
|
259
|
-
existing_data = {}
|
|
260
|
-
|
|
261
|
-
# 只更新指定的键
|
|
262
|
-
for key in update_keys:
|
|
263
|
-
if key in data:
|
|
264
|
-
existing_data[key] = data[key]
|
|
265
|
-
data = existing_data
|
|
266
|
-
|
|
267
|
-
with open(file_path, 'w', encoding='utf-8') as f:
|
|
268
|
-
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
269
|
-
|
|
270
|
-
######################################################################################################
|
|
271
|
-
def getTaskStoreKey(key_id, store_name):
|
|
272
|
-
return f'{key_id}_{store_name}'
|
|
273
|
-
|
|
274
|
-
def generate_progress_file(config, key_id):
|
|
275
|
-
return f'{config.auto_dir}/progress/progress_{key_id}.json'
|
|
276
|
-
|
|
277
|
-
def get_progress_index_ex(config, task_key, store_name):
|
|
278
|
-
task_store_key = getTaskStoreKey(task_key, store_name)
|
|
279
|
-
progress_file = generate_progress_file(config, task_key)
|
|
280
|
-
dict = read_dict_from_file(progress_file)
|
|
281
|
-
if len(dict) > 0:
|
|
282
|
-
count = 0
|
|
283
|
-
for key, value in dict.items():
|
|
284
|
-
if key == task_store_key:
|
|
285
|
-
return count
|
|
286
|
-
count += 1
|
|
287
|
-
return len(dict)
|
|
288
|
-
|
|
289
|
-
def get_progress_json_ex(config, task_key, store_name):
|
|
290
|
-
task_store_key = getTaskStoreKey(task_key, store_name)
|
|
291
|
-
progress_file = generate_progress_file(config, task_key)
|
|
292
|
-
dict = read_dict_from_file_ex(progress_file, task_store_key)
|
|
293
|
-
if len(dict) > 0:
|
|
294
|
-
return dict[0] == 1
|
|
295
|
-
else:
|
|
296
|
-
length = get_progress_index_ex(config, task_key, store_name)
|
|
297
|
-
write_dict_to_file_ex(progress_file, {task_store_key: [0, length + 1, datetime.now().strftime('%Y-%m-%d %H:%M:%S')]}, [task_store_key])
|
|
298
|
-
return False
|
|
299
|
-
|
|
300
|
-
def done_progress_json_ex(config, task_key, store_name):
|
|
301
|
-
task_store_key = getTaskStoreKey(task_key, store_name)
|
|
302
|
-
progress_file = generate_progress_file(config, task_key)
|
|
303
|
-
length = get_progress_index_ex(config, task_key, store_name)
|
|
304
|
-
write_dict_to_file_ex(progress_file, {task_store_key: [1, length + 1, datetime.now().strftime('%Y-%m-%d %H:%M:%S')]}, [task_store_key])
|
|
305
|
-
|
|
306
|
-
def check_progress_json_ex(config, task_key, just_store_username=None):
|
|
307
|
-
progress_file = generate_progress_file(config, task_key)
|
|
308
|
-
dict = read_dict_from_file(progress_file)
|
|
309
|
-
if len(dict) > 0:
|
|
310
|
-
for task_store_key, data_list in dict.items():
|
|
311
|
-
if just_store_username and len(just_store_username) > 0:
|
|
312
|
-
if all([store_username not in task_store_key for store_username in just_store_username]):
|
|
313
|
-
continue
|
|
314
|
-
if 'run_' not in task_store_key and int(data_list[0]) == 0:
|
|
315
|
-
log(task_store_key, just_store_username)
|
|
316
|
-
return False
|
|
317
|
-
else:
|
|
318
|
-
log(f"进度文件不存在或为空: {progress_file}")
|
|
1
|
+
import os
|
|
2
|
+
import threading
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
file_lock = threading.Lock() # 线程锁
|
|
6
|
+
|
|
7
|
+
from datetime import date, datetime, timedelta, timezone
|
|
8
|
+
|
|
9
|
+
from .fun_base import log
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import gc
|
|
13
|
+
import time
|
|
14
|
+
import psutil
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
def delete_file(file_path):
|
|
18
|
+
"""
|
|
19
|
+
删除文件的优化函数,自动处理已打开的文件
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
file_path (str): 要删除的文件路径
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
bool: 删除成功返回True,失败返回False
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
file_path = Path(file_path).resolve() # 规范化路径
|
|
29
|
+
|
|
30
|
+
if not file_path.exists():
|
|
31
|
+
log(f"文件 {file_path} 不存在。")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
# 强制垃圾回收,关闭可能的文件句柄
|
|
35
|
+
gc.collect()
|
|
36
|
+
|
|
37
|
+
# 第一次尝试直接删除
|
|
38
|
+
try:
|
|
39
|
+
os.remove(file_path)
|
|
40
|
+
log(f"文件 {file_path} 已成功删除。")
|
|
41
|
+
return True
|
|
42
|
+
except PermissionError:
|
|
43
|
+
log(f"文件 {file_path} 可能被占用,尝试关闭相关进程...")
|
|
44
|
+
|
|
45
|
+
# 尝试找到并关闭占用该文件的进程
|
|
46
|
+
if close_file_handles(file_path):
|
|
47
|
+
# 等待一小段时间让系统释放句柄
|
|
48
|
+
time.sleep(0.1)
|
|
49
|
+
|
|
50
|
+
# 再次尝试删除
|
|
51
|
+
try:
|
|
52
|
+
os.remove(file_path)
|
|
53
|
+
log(f"文件 {file_path} 已成功删除。")
|
|
54
|
+
return True
|
|
55
|
+
except PermissionError:
|
|
56
|
+
log(f"错误:即使尝试关闭文件句柄后,仍无法删除文件 {file_path}。")
|
|
57
|
+
return False
|
|
58
|
+
else:
|
|
59
|
+
log(f"错误:无法关闭文件 {file_path} 的句柄,删除失败。")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
except FileNotFoundError:
|
|
63
|
+
log(f"错误:文件 {file_path} 未找到。")
|
|
64
|
+
return False
|
|
65
|
+
except Exception as e:
|
|
66
|
+
log(f"错误:删除文件时发生未知错误:{e}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def close_file_handles(file_path):
|
|
70
|
+
"""
|
|
71
|
+
尝试关闭指定文件的所有句柄
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
file_path (Path): 文件路径
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
bool: 成功关闭返回True,失败返回False
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
file_path_str = str(file_path)
|
|
81
|
+
closed_any = False
|
|
82
|
+
|
|
83
|
+
# 遍历所有进程,查找打开该文件的进程
|
|
84
|
+
for proc in psutil.process_iter(['pid', 'name']):
|
|
85
|
+
try:
|
|
86
|
+
# 获取进程打开的文件列表
|
|
87
|
+
open_files = proc.open_files()
|
|
88
|
+
for open_file in open_files:
|
|
89
|
+
if os.path.samefile(open_file.path, file_path_str):
|
|
90
|
+
log(f"发现进程 {proc.info['name']} (PID: {proc.info['pid']}) 正在使用文件")
|
|
91
|
+
|
|
92
|
+
# 如果是当前Python进程,尝试强制关闭文件句柄
|
|
93
|
+
if proc.info['pid'] == os.getpid():
|
|
94
|
+
closed_any = True
|
|
95
|
+
else:
|
|
96
|
+
# 对于其他进程,可以选择终止(谨慎使用)
|
|
97
|
+
log(f"警告:文件被其他进程占用,请手动关闭应用程序")
|
|
98
|
+
|
|
99
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
100
|
+
continue
|
|
101
|
+
except Exception as e:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
return closed_any
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
log(f"关闭文件句柄时出错:{e}")
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
def delete_file_simple(file_path):
|
|
111
|
+
"""
|
|
112
|
+
简化版本的删除文件函数(不需要额外依赖)
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
file_path (str): 要删除的文件路径
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bool: 删除成功返回True,失败返回False
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
file_path = os.path.abspath(file_path) # 获取绝对路径
|
|
122
|
+
|
|
123
|
+
if not os.path.exists(file_path):
|
|
124
|
+
log(f"文件 {file_path} 不存在。")
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
# 强制垃圾回收
|
|
128
|
+
gc.collect()
|
|
129
|
+
|
|
130
|
+
# 尝试删除
|
|
131
|
+
try:
|
|
132
|
+
os.remove(file_path)
|
|
133
|
+
log(f"文件 {file_path} 已成功删除。")
|
|
134
|
+
return True
|
|
135
|
+
except PermissionError:
|
|
136
|
+
# 在Windows上,尝试修改文件权限
|
|
137
|
+
if os.name == 'nt': # Windows
|
|
138
|
+
try:
|
|
139
|
+
os.chmod(file_path, 0o777)
|
|
140
|
+
time.sleep(0.1) # 短暂等待
|
|
141
|
+
os.remove(file_path)
|
|
142
|
+
log(f"文件 {file_path} 已成功删除。")
|
|
143
|
+
return True
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
log(f"错误:没有权限删除文件 {file_path},可能文件正在被使用。")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
except FileNotFoundError:
|
|
151
|
+
log(f"错误:文件 {file_path} 未找到。")
|
|
152
|
+
return False
|
|
153
|
+
except Exception as e:
|
|
154
|
+
log(f"错误:删除文件时发生未知错误:{e}")
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
def read_dict_from_file(file_path, cache_interval=3600 * 24 * 365 * 10):
|
|
158
|
+
"""
|
|
159
|
+
从文件中读取字典。
|
|
160
|
+
如果文件的修改时间未超过一个小时,则返回字典;否则返回 None。
|
|
161
|
+
|
|
162
|
+
:param file_path: 文件路径
|
|
163
|
+
:return: 字典或 None
|
|
164
|
+
"""
|
|
165
|
+
with file_lock: # 使用锁保护文件操作
|
|
166
|
+
# 检查文件是否存在
|
|
167
|
+
if not os.path.exists(file_path):
|
|
168
|
+
return {}
|
|
169
|
+
|
|
170
|
+
# 获取文件的最后修改时间
|
|
171
|
+
modification_time = os.path.getmtime(file_path)
|
|
172
|
+
modification_time = datetime.fromtimestamp(modification_time)
|
|
173
|
+
|
|
174
|
+
# 获取当前时间
|
|
175
|
+
current_time = datetime.now()
|
|
176
|
+
|
|
177
|
+
interval = current_time - modification_time
|
|
178
|
+
log(f'缓存文件 {file_path} 缓存时长 {timedelta(seconds=int(cache_interval))} 已过时长 {interval}')
|
|
179
|
+
|
|
180
|
+
# 判断文件的修改时间是否超过一个小时
|
|
181
|
+
if interval <= timedelta(seconds=int(cache_interval)):
|
|
182
|
+
# 如果未超过一个小时,则读取文件内容
|
|
183
|
+
with open(file_path, "r", encoding='utf-8') as file:
|
|
184
|
+
return json.load(file)
|
|
185
|
+
else:
|
|
186
|
+
# 如果超过一个小时,则返回 None
|
|
187
|
+
return {}
|
|
188
|
+
|
|
189
|
+
def write_dict_to_file(file_path, data):
|
|
190
|
+
"""
|
|
191
|
+
将字典写入文件。
|
|
192
|
+
|
|
193
|
+
:param file_path: 文件路径
|
|
194
|
+
:param data: 要写入的字典
|
|
195
|
+
"""
|
|
196
|
+
with file_lock: # 使用锁保护文件操作
|
|
197
|
+
# 确保目标文件夹存在
|
|
198
|
+
dir_name = os.path.dirname(file_path)
|
|
199
|
+
if dir_name and not os.path.exists(dir_name):
|
|
200
|
+
os.makedirs(dir_name, exist_ok=True) # 递归创建目录
|
|
201
|
+
|
|
202
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
203
|
+
# 使用 json.dump() 并设置 ensure_ascii=False
|
|
204
|
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
205
|
+
|
|
206
|
+
def read_dict_from_file_ex(file_path, key, cache_interval=3600 * 24 * 365 * 10, default='dict'):
|
|
207
|
+
"""
|
|
208
|
+
从 JSON 文件中读取指定键的值。
|
|
209
|
+
|
|
210
|
+
:param file_path: JSON 文件路径
|
|
211
|
+
:param key: 要读取的键
|
|
212
|
+
:param default: 如果文件不存在、解析失败或键不存在时返回的默认值
|
|
213
|
+
:return: 对应键的值,或 default
|
|
214
|
+
"""
|
|
215
|
+
with file_lock: # 使用锁保护文件操作
|
|
216
|
+
if not os.path.exists(file_path):
|
|
217
|
+
return {} if default == 'dict' else []
|
|
218
|
+
|
|
219
|
+
# 获取文件的最后修改时间
|
|
220
|
+
modification_time = os.path.getmtime(file_path)
|
|
221
|
+
modification_time = datetime.fromtimestamp(modification_time)
|
|
222
|
+
|
|
223
|
+
# 获取当前时间
|
|
224
|
+
current_time = datetime.now()
|
|
225
|
+
|
|
226
|
+
interval = current_time - modification_time
|
|
227
|
+
log(f'缓存文件 {file_path} 缓存时长 {timedelta(seconds=cache_interval)} 已过时长 {interval}')
|
|
228
|
+
|
|
229
|
+
# 判断文件的修改时间是否超过一个小时
|
|
230
|
+
if interval <= timedelta(seconds=cache_interval):
|
|
231
|
+
# 如果未超过一个小时,则读取文件内容
|
|
232
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
233
|
+
data = json.load(f)
|
|
234
|
+
return data.get(key, {})
|
|
235
|
+
else:
|
|
236
|
+
# 如果超过一个小时,则返回 None
|
|
237
|
+
return {} if default == 'dict' else []
|
|
238
|
+
|
|
239
|
+
def write_dict_to_file_ex(file_path, data, update_keys=None):
|
|
240
|
+
"""
|
|
241
|
+
将字典写入文件,可选择性地只更新指定键。
|
|
242
|
+
|
|
243
|
+
:param file_path: 文件路径
|
|
244
|
+
:param data: 要写入的字典数据
|
|
245
|
+
:param update_keys: 可选,需要更新的键列表。如果为None,则替换整个文件内容
|
|
246
|
+
"""
|
|
247
|
+
with file_lock: # 使用锁保护文件操作
|
|
248
|
+
# 确保目标文件夹存在
|
|
249
|
+
dir_name = os.path.dirname(file_path)
|
|
250
|
+
if dir_name and not os.path.exists(dir_name):
|
|
251
|
+
os.makedirs(dir_name, exist_ok=True) # 递归创建目录
|
|
252
|
+
|
|
253
|
+
# 如果指定了update_keys,先读取现有数据然后合并
|
|
254
|
+
if update_keys is not None:
|
|
255
|
+
try:
|
|
256
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
257
|
+
existing_data = json.load(f)
|
|
258
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
259
|
+
existing_data = {}
|
|
260
|
+
|
|
261
|
+
# 只更新指定的键
|
|
262
|
+
for key in update_keys:
|
|
263
|
+
if key in data:
|
|
264
|
+
existing_data[key] = data[key]
|
|
265
|
+
data = existing_data
|
|
266
|
+
|
|
267
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
268
|
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
269
|
+
|
|
270
|
+
######################################################################################################
|
|
271
|
+
def getTaskStoreKey(key_id, store_name):
|
|
272
|
+
return f'{key_id}_{store_name}'
|
|
273
|
+
|
|
274
|
+
def generate_progress_file(config, key_id):
|
|
275
|
+
return f'{config.auto_dir}/progress/progress_{key_id}.json'
|
|
276
|
+
|
|
277
|
+
def get_progress_index_ex(config, task_key, store_name):
|
|
278
|
+
task_store_key = getTaskStoreKey(task_key, store_name)
|
|
279
|
+
progress_file = generate_progress_file(config, task_key)
|
|
280
|
+
dict = read_dict_from_file(progress_file)
|
|
281
|
+
if len(dict) > 0:
|
|
282
|
+
count = 0
|
|
283
|
+
for key, value in dict.items():
|
|
284
|
+
if key == task_store_key:
|
|
285
|
+
return count
|
|
286
|
+
count += 1
|
|
287
|
+
return len(dict)
|
|
288
|
+
|
|
289
|
+
def get_progress_json_ex(config, task_key, store_name):
|
|
290
|
+
task_store_key = getTaskStoreKey(task_key, store_name)
|
|
291
|
+
progress_file = generate_progress_file(config, task_key)
|
|
292
|
+
dict = read_dict_from_file_ex(progress_file, task_store_key)
|
|
293
|
+
if len(dict) > 0:
|
|
294
|
+
return dict[0] == 1
|
|
295
|
+
else:
|
|
296
|
+
length = get_progress_index_ex(config, task_key, store_name)
|
|
297
|
+
write_dict_to_file_ex(progress_file, {task_store_key: [0, length + 1, datetime.now().strftime('%Y-%m-%d %H:%M:%S')]}, [task_store_key])
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
def done_progress_json_ex(config, task_key, store_name):
|
|
301
|
+
task_store_key = getTaskStoreKey(task_key, store_name)
|
|
302
|
+
progress_file = generate_progress_file(config, task_key)
|
|
303
|
+
length = get_progress_index_ex(config, task_key, store_name)
|
|
304
|
+
write_dict_to_file_ex(progress_file, {task_store_key: [1, length + 1, datetime.now().strftime('%Y-%m-%d %H:%M:%S')]}, [task_store_key])
|
|
305
|
+
|
|
306
|
+
def check_progress_json_ex(config, task_key, just_store_username=None):
|
|
307
|
+
progress_file = generate_progress_file(config, task_key)
|
|
308
|
+
dict = read_dict_from_file(progress_file)
|
|
309
|
+
if len(dict) > 0:
|
|
310
|
+
for task_store_key, data_list in dict.items():
|
|
311
|
+
if just_store_username and len(just_store_username) > 0:
|
|
312
|
+
if all([store_username not in task_store_key for store_username in just_store_username]):
|
|
313
|
+
continue
|
|
314
|
+
if 'run_' not in task_store_key and int(data_list[0]) == 0:
|
|
315
|
+
log(task_store_key, just_store_username)
|
|
316
|
+
return False
|
|
317
|
+
else:
|
|
318
|
+
log(f"进度文件不存在或为空: {progress_file}")
|
|
319
319
|
return True
|