qrpa 1.1.33__py3-none-any.whl → 1.1.35__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.

Potentially problematic release.


This version of qrpa might be problematic. Click here for more details.

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