auto-coder 0.1.363__py3-none-any.whl → 0.1.364__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 auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/METADATA +2 -2
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/RECORD +33 -18
- autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +1 -1
- autocoder/auto_coder_runner.py +2 -0
- autocoder/common/__init__.py +2 -0
- autocoder/common/file_checkpoint/__init__.py +21 -0
- autocoder/common/file_checkpoint/backup.py +264 -0
- autocoder/common/file_checkpoint/examples.py +217 -0
- autocoder/common/file_checkpoint/manager.py +404 -0
- autocoder/common/file_checkpoint/models.py +156 -0
- autocoder/common/file_checkpoint/store.py +383 -0
- autocoder/common/file_checkpoint/test_backup.py +242 -0
- autocoder/common/file_checkpoint/test_manager.py +570 -0
- autocoder/common/file_checkpoint/test_models.py +360 -0
- autocoder/common/file_checkpoint/test_store.py +327 -0
- autocoder/common/file_checkpoint/test_utils.py +297 -0
- autocoder/common/file_checkpoint/utils.py +119 -0
- autocoder/common/rulefiles/autocoderrules_utils.py +114 -55
- autocoder/common/save_formatted_log.py +76 -5
- autocoder/common/v2/agent/agentic_edit.py +318 -197
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +2 -2
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +27 -4
- autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
- autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +83 -61
- autocoder/compilers/normal_compiler.py +64 -0
- autocoder/events/event_manager_singleton.py +133 -4
- autocoder/linters/normal_linter.py +373 -0
- autocoder/linters/python_linter.py +4 -2
- autocoder/version.py +1 -1
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
文件变更管理模块的数据模型
|
|
3
|
+
|
|
4
|
+
定义了表示文件变更、变更记录和操作结果的数据类。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import uuid
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Dict, List, Optional, Union
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class FileChange:
|
|
16
|
+
"""表示单个文件的变更信息"""
|
|
17
|
+
file_path: str
|
|
18
|
+
content: str
|
|
19
|
+
is_new: bool = False
|
|
20
|
+
is_deletion: bool = False
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_dict(cls, data: Dict) -> 'FileChange':
|
|
24
|
+
"""从字典创建FileChange对象"""
|
|
25
|
+
return cls(
|
|
26
|
+
file_path=data['file_path'],
|
|
27
|
+
content=data['content'],
|
|
28
|
+
is_new=data.get('is_new', False),
|
|
29
|
+
is_deletion=data.get('is_deletion', False)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> Dict:
|
|
33
|
+
"""将FileChange对象转换为字典"""
|
|
34
|
+
return {
|
|
35
|
+
'file_path': self.file_path,
|
|
36
|
+
'content': self.content,
|
|
37
|
+
'is_new': self.is_new,
|
|
38
|
+
'is_deletion': self.is_deletion
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ChangeRecord:
|
|
44
|
+
"""表示一条变更记录,包含变更的元数据和详细信息"""
|
|
45
|
+
change_id: str
|
|
46
|
+
timestamp: float
|
|
47
|
+
file_path: str
|
|
48
|
+
backup_id: Optional[str]
|
|
49
|
+
is_new: bool = False
|
|
50
|
+
is_deletion: bool = False
|
|
51
|
+
group_id: Optional[str] = None
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def create(cls, file_path: str, backup_id: Optional[str],
|
|
55
|
+
is_new: bool = False, is_deletion: bool = False,
|
|
56
|
+
group_id: Optional[str] = None) -> 'ChangeRecord':
|
|
57
|
+
"""创建一个新的变更记录"""
|
|
58
|
+
return cls(
|
|
59
|
+
change_id=str(uuid.uuid4()),
|
|
60
|
+
timestamp=datetime.now().timestamp(),
|
|
61
|
+
file_path=file_path,
|
|
62
|
+
backup_id=backup_id,
|
|
63
|
+
is_new=is_new,
|
|
64
|
+
is_deletion=is_deletion,
|
|
65
|
+
group_id=group_id
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_dict(cls, data: Dict) -> 'ChangeRecord':
|
|
70
|
+
"""从字典创建ChangeRecord对象"""
|
|
71
|
+
return cls(
|
|
72
|
+
change_id=data['change_id'],
|
|
73
|
+
timestamp=data['timestamp'],
|
|
74
|
+
file_path=data['file_path'],
|
|
75
|
+
backup_id=data.get('backup_id'),
|
|
76
|
+
is_new=data.get('is_new', False),
|
|
77
|
+
is_deletion=data.get('is_deletion', False),
|
|
78
|
+
group_id=data.get('group_id')
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def to_dict(self) -> Dict:
|
|
82
|
+
"""将ChangeRecord对象转换为字典"""
|
|
83
|
+
return {
|
|
84
|
+
'change_id': self.change_id,
|
|
85
|
+
'timestamp': self.timestamp,
|
|
86
|
+
'file_path': self.file_path,
|
|
87
|
+
'backup_id': self.backup_id,
|
|
88
|
+
'is_new': self.is_new,
|
|
89
|
+
'is_deletion': self.is_deletion,
|
|
90
|
+
'group_id': self.group_id
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class DiffResult:
|
|
96
|
+
"""表示文件差异比较的结果"""
|
|
97
|
+
file_path: str
|
|
98
|
+
old_content: Optional[str]
|
|
99
|
+
new_content: str
|
|
100
|
+
is_new: bool = False
|
|
101
|
+
is_deletion: bool = False
|
|
102
|
+
|
|
103
|
+
def get_diff_summary(self) -> str:
|
|
104
|
+
"""获取差异摘要"""
|
|
105
|
+
if self.is_new:
|
|
106
|
+
return f"新文件: {self.file_path}"
|
|
107
|
+
elif self.is_deletion:
|
|
108
|
+
return f"删除文件: {self.file_path}"
|
|
109
|
+
else:
|
|
110
|
+
old_lines = 0 if self.old_content is None else len(self.old_content.splitlines())
|
|
111
|
+
new_lines = len(self.new_content.splitlines())
|
|
112
|
+
return f"修改文件: {self.file_path} (原始行数: {old_lines}, 新行数: {new_lines})"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class ApplyResult:
|
|
117
|
+
"""表示变更应用的结果"""
|
|
118
|
+
success: bool
|
|
119
|
+
change_ids: List[str] = field(default_factory=list)
|
|
120
|
+
errors: Dict[str, str] = field(default_factory=dict)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def has_errors(self) -> bool:
|
|
124
|
+
"""是否有错误"""
|
|
125
|
+
return len(self.errors) > 0
|
|
126
|
+
|
|
127
|
+
def add_error(self, file_path: str, error_message: str) -> None:
|
|
128
|
+
"""添加错误信息"""
|
|
129
|
+
self.errors[file_path] = error_message
|
|
130
|
+
self.success = False
|
|
131
|
+
|
|
132
|
+
def add_change_id(self, change_id: str) -> None:
|
|
133
|
+
"""添加成功应用的变更ID"""
|
|
134
|
+
self.change_ids.append(change_id)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass
|
|
138
|
+
class UndoResult:
|
|
139
|
+
"""表示变更撤销的结果"""
|
|
140
|
+
success: bool
|
|
141
|
+
restored_files: List[str] = field(default_factory=list)
|
|
142
|
+
errors: Dict[str, str] = field(default_factory=dict)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def has_errors(self) -> bool:
|
|
146
|
+
"""是否有错误"""
|
|
147
|
+
return len(self.errors) > 0
|
|
148
|
+
|
|
149
|
+
def add_error(self, file_path: str, error_message: str) -> None:
|
|
150
|
+
"""添加错误信息"""
|
|
151
|
+
self.errors[file_path] = error_message
|
|
152
|
+
self.success = False
|
|
153
|
+
|
|
154
|
+
def add_restored_file(self, file_path: str) -> None:
|
|
155
|
+
"""添加成功恢复的文件"""
|
|
156
|
+
self.restored_files.append(file_path)
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""
|
|
2
|
+
文件变更存储器
|
|
3
|
+
|
|
4
|
+
负责存储和管理文件变更历史记录,支持按组、按时间等方式查询变更记录。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
import sqlite3
|
|
10
|
+
import logging
|
|
11
|
+
import threading
|
|
12
|
+
from typing import Dict, List, Optional, Tuple, Any
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
from autocoder.common.file_checkpoint.models import ChangeRecord
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FileChangeStore:
|
|
21
|
+
"""负责存储和管理文件变更历史记录"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, store_dir: Optional[str] = None, max_history: int = 50):
|
|
24
|
+
"""
|
|
25
|
+
初始化变更存储
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
store_dir: 存储目录,如果为None则使用默认目录
|
|
29
|
+
max_history: 最大保存的历史版本数量
|
|
30
|
+
"""
|
|
31
|
+
if store_dir is None:
|
|
32
|
+
# 默认存储目录为项目根目录下的.auto-coder/checkpoint
|
|
33
|
+
store_dir = os.path.join(os.getcwd(), ".auto-coder", "checkpoint")
|
|
34
|
+
|
|
35
|
+
self.store_dir = store_dir
|
|
36
|
+
self.max_history = max_history
|
|
37
|
+
self.db_file = os.path.join(store_dir, "changes.db")
|
|
38
|
+
self.lock = threading.RLock()
|
|
39
|
+
|
|
40
|
+
# 确保存储目录存在
|
|
41
|
+
os.makedirs(store_dir, exist_ok=True)
|
|
42
|
+
|
|
43
|
+
# 初始化数据库
|
|
44
|
+
self._init_db()
|
|
45
|
+
|
|
46
|
+
def save_change(self, change_record: ChangeRecord) -> str:
|
|
47
|
+
"""
|
|
48
|
+
保存一条变更记录
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
change_record: 变更记录对象
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
str: 变更记录ID
|
|
55
|
+
"""
|
|
56
|
+
with self.lock:
|
|
57
|
+
try:
|
|
58
|
+
# 将变更记录保存到数据库
|
|
59
|
+
conn = self._get_db_connection()
|
|
60
|
+
cursor = conn.cursor()
|
|
61
|
+
|
|
62
|
+
cursor.execute(
|
|
63
|
+
"""
|
|
64
|
+
INSERT INTO changes
|
|
65
|
+
(change_id, timestamp, file_path, backup_id, is_new, is_deletion, group_id, data)
|
|
66
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
67
|
+
""",
|
|
68
|
+
(
|
|
69
|
+
change_record.change_id,
|
|
70
|
+
change_record.timestamp,
|
|
71
|
+
change_record.file_path,
|
|
72
|
+
change_record.backup_id,
|
|
73
|
+
int(change_record.is_new),
|
|
74
|
+
int(change_record.is_deletion),
|
|
75
|
+
change_record.group_id,
|
|
76
|
+
json.dumps(change_record.to_dict())
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
conn.commit()
|
|
81
|
+
|
|
82
|
+
# 保存变更记录到JSON文件
|
|
83
|
+
self._save_change_to_file(change_record)
|
|
84
|
+
|
|
85
|
+
# 清理过旧的历史记录
|
|
86
|
+
self._clean_old_history()
|
|
87
|
+
|
|
88
|
+
logger.debug(f"已保存变更记录 {change_record.change_id}")
|
|
89
|
+
return change_record.change_id
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"保存变更记录失败: {str(e)}")
|
|
93
|
+
raise
|
|
94
|
+
|
|
95
|
+
def get_change(self, change_id: str) -> Optional[ChangeRecord]:
|
|
96
|
+
"""
|
|
97
|
+
获取指定ID的变更记录
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
change_id: 变更记录ID
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
ChangeRecord: 变更记录对象,如果不存在则返回None
|
|
104
|
+
"""
|
|
105
|
+
with self.lock:
|
|
106
|
+
try:
|
|
107
|
+
conn = self._get_db_connection()
|
|
108
|
+
cursor = conn.cursor()
|
|
109
|
+
|
|
110
|
+
cursor.execute(
|
|
111
|
+
"SELECT data FROM changes WHERE change_id = ?",
|
|
112
|
+
(change_id,)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
row = cursor.fetchone()
|
|
116
|
+
if row is None:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
data = json.loads(row[0])
|
|
120
|
+
return ChangeRecord.from_dict(data)
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"获取变更记录 {change_id} 失败: {str(e)}")
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
def get_changes_by_group(self, group_id: str) -> List[ChangeRecord]:
|
|
127
|
+
"""
|
|
128
|
+
获取指定组的所有变更记录
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
group_id: 变更组ID
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List[ChangeRecord]: 变更记录列表
|
|
135
|
+
"""
|
|
136
|
+
with self.lock:
|
|
137
|
+
try:
|
|
138
|
+
conn = self._get_db_connection()
|
|
139
|
+
cursor = conn.cursor()
|
|
140
|
+
|
|
141
|
+
cursor.execute(
|
|
142
|
+
"SELECT data FROM changes WHERE group_id = ? ORDER BY timestamp DESC",
|
|
143
|
+
(group_id,)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
changes = []
|
|
147
|
+
for row in cursor.fetchall():
|
|
148
|
+
data = json.loads(row[0])
|
|
149
|
+
changes.append(ChangeRecord.from_dict(data))
|
|
150
|
+
|
|
151
|
+
return changes
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.error(f"获取变更组 {group_id} 的记录失败: {str(e)}")
|
|
155
|
+
return []
|
|
156
|
+
|
|
157
|
+
def get_latest_changes(self, limit: int = 10) -> List[ChangeRecord]:
|
|
158
|
+
"""
|
|
159
|
+
获取最近的变更记录
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
limit: 返回的记录数量限制
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List[ChangeRecord]: 变更记录列表
|
|
166
|
+
"""
|
|
167
|
+
with self.lock:
|
|
168
|
+
try:
|
|
169
|
+
conn = self._get_db_connection()
|
|
170
|
+
cursor = conn.cursor()
|
|
171
|
+
|
|
172
|
+
cursor.execute(
|
|
173
|
+
"SELECT data FROM changes ORDER BY timestamp DESC LIMIT ?",
|
|
174
|
+
(limit,)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
changes = []
|
|
178
|
+
for row in cursor.fetchall():
|
|
179
|
+
data = json.loads(row[0])
|
|
180
|
+
changes.append(ChangeRecord.from_dict(data))
|
|
181
|
+
|
|
182
|
+
return changes
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.error(f"获取最近变更记录失败: {str(e)}")
|
|
186
|
+
return []
|
|
187
|
+
|
|
188
|
+
def get_changes_by_file(self, file_path: str, limit: int = 10) -> List[ChangeRecord]:
|
|
189
|
+
"""
|
|
190
|
+
获取指定文件的变更记录
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
file_path: 文件路径
|
|
194
|
+
limit: 返回的记录数量限制
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
List[ChangeRecord]: 变更记录列表
|
|
198
|
+
"""
|
|
199
|
+
with self.lock:
|
|
200
|
+
try:
|
|
201
|
+
conn = self._get_db_connection()
|
|
202
|
+
cursor = conn.cursor()
|
|
203
|
+
|
|
204
|
+
cursor.execute(
|
|
205
|
+
"SELECT data FROM changes WHERE file_path = ? ORDER BY timestamp DESC LIMIT ?",
|
|
206
|
+
(file_path, limit)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
changes = []
|
|
210
|
+
for row in cursor.fetchall():
|
|
211
|
+
data = json.loads(row[0])
|
|
212
|
+
changes.append(ChangeRecord.from_dict(data))
|
|
213
|
+
|
|
214
|
+
return changes
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
logger.error(f"获取文件 {file_path} 的变更记录失败: {str(e)}")
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
def delete_change(self, change_id: str) -> bool:
|
|
221
|
+
"""
|
|
222
|
+
删除指定的变更记录
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
change_id: 变更记录ID
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
bool: 删除是否成功
|
|
229
|
+
"""
|
|
230
|
+
with self.lock:
|
|
231
|
+
try:
|
|
232
|
+
# 获取变更记录
|
|
233
|
+
change = self.get_change(change_id)
|
|
234
|
+
if change is None:
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
# 删除数据库中的记录
|
|
238
|
+
conn = self._get_db_connection()
|
|
239
|
+
cursor = conn.cursor()
|
|
240
|
+
|
|
241
|
+
cursor.execute(
|
|
242
|
+
"DELETE FROM changes WHERE change_id = ?",
|
|
243
|
+
(change_id,)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
conn.commit()
|
|
247
|
+
|
|
248
|
+
# 删除JSON文件
|
|
249
|
+
json_file = os.path.join(self.store_dir, f"{change_id}.json")
|
|
250
|
+
if os.path.exists(json_file):
|
|
251
|
+
os.remove(json_file)
|
|
252
|
+
|
|
253
|
+
logger.debug(f"已删除变更记录 {change_id}")
|
|
254
|
+
return True
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
logger.error(f"删除变更记录 {change_id} 失败: {str(e)}")
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
def get_change_groups(self, limit: int = 10) -> List[Tuple[str, float, int]]:
|
|
261
|
+
"""
|
|
262
|
+
获取变更组列表
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
limit: 返回的组数量限制
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
List[Tuple[str, float, int]]: 变更组ID、最新时间戳和变更数量的列表
|
|
269
|
+
"""
|
|
270
|
+
with self.lock:
|
|
271
|
+
try:
|
|
272
|
+
conn = self._get_db_connection()
|
|
273
|
+
cursor = conn.cursor()
|
|
274
|
+
|
|
275
|
+
cursor.execute(
|
|
276
|
+
"""
|
|
277
|
+
SELECT group_id, MAX(timestamp) as latest_time, COUNT(*) as count
|
|
278
|
+
FROM changes
|
|
279
|
+
WHERE group_id IS NOT NULL
|
|
280
|
+
GROUP BY group_id
|
|
281
|
+
ORDER BY latest_time DESC
|
|
282
|
+
LIMIT ?
|
|
283
|
+
""",
|
|
284
|
+
(limit,)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
groups = []
|
|
288
|
+
for row in cursor.fetchall():
|
|
289
|
+
groups.append((row[0], row[1], row[2]))
|
|
290
|
+
|
|
291
|
+
return groups
|
|
292
|
+
|
|
293
|
+
except Exception as e:
|
|
294
|
+
logger.error(f"获取变更组列表失败: {str(e)}")
|
|
295
|
+
return []
|
|
296
|
+
|
|
297
|
+
def _init_db(self) -> None:
|
|
298
|
+
"""初始化数据库"""
|
|
299
|
+
try:
|
|
300
|
+
conn = self._get_db_connection()
|
|
301
|
+
cursor = conn.cursor()
|
|
302
|
+
|
|
303
|
+
# 创建变更记录表
|
|
304
|
+
cursor.execute(
|
|
305
|
+
"""
|
|
306
|
+
CREATE TABLE IF NOT EXISTS changes (
|
|
307
|
+
change_id TEXT PRIMARY KEY,
|
|
308
|
+
timestamp REAL NOT NULL,
|
|
309
|
+
file_path TEXT NOT NULL,
|
|
310
|
+
backup_id TEXT,
|
|
311
|
+
is_new INTEGER NOT NULL DEFAULT 0,
|
|
312
|
+
is_deletion INTEGER NOT NULL DEFAULT 0,
|
|
313
|
+
group_id TEXT,
|
|
314
|
+
data TEXT NOT NULL
|
|
315
|
+
)
|
|
316
|
+
"""
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# 创建索引
|
|
320
|
+
cursor.execute(
|
|
321
|
+
"CREATE INDEX IF NOT EXISTS idx_changes_timestamp ON changes (timestamp)"
|
|
322
|
+
)
|
|
323
|
+
cursor.execute(
|
|
324
|
+
"CREATE INDEX IF NOT EXISTS idx_changes_file_path ON changes (file_path)"
|
|
325
|
+
)
|
|
326
|
+
cursor.execute(
|
|
327
|
+
"CREATE INDEX IF NOT EXISTS idx_changes_group_id ON changes (group_id)"
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
conn.commit()
|
|
331
|
+
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logger.error(f"初始化数据库失败: {str(e)}")
|
|
334
|
+
raise
|
|
335
|
+
|
|
336
|
+
def _get_db_connection(self) -> sqlite3.Connection:
|
|
337
|
+
"""获取数据库连接"""
|
|
338
|
+
conn = sqlite3.connect(self.db_file)
|
|
339
|
+
conn.row_factory = sqlite3.Row
|
|
340
|
+
return conn
|
|
341
|
+
|
|
342
|
+
def _save_change_to_file(self, change_record: ChangeRecord) -> None:
|
|
343
|
+
"""将变更记录保存到JSON文件"""
|
|
344
|
+
try:
|
|
345
|
+
json_file = os.path.join(self.store_dir, f"{change_record.change_id}.json")
|
|
346
|
+
|
|
347
|
+
with open(json_file, 'w', encoding='utf-8') as f:
|
|
348
|
+
json.dump(change_record.to_dict(), f, indent=2)
|
|
349
|
+
|
|
350
|
+
except Exception as e:
|
|
351
|
+
logger.error(f"保存变更记录到文件失败: {str(e)}")
|
|
352
|
+
|
|
353
|
+
def _clean_old_history(self) -> None:
|
|
354
|
+
"""清理过旧的历史记录"""
|
|
355
|
+
try:
|
|
356
|
+
conn = self._get_db_connection()
|
|
357
|
+
cursor = conn.cursor()
|
|
358
|
+
|
|
359
|
+
# 获取记录总数
|
|
360
|
+
cursor.execute("SELECT COUNT(*) FROM changes")
|
|
361
|
+
total_count = cursor.fetchone()[0]
|
|
362
|
+
|
|
363
|
+
# 如果记录数超过最大限制,删除最旧的记录
|
|
364
|
+
if total_count > self.max_history:
|
|
365
|
+
# 计算需要删除的记录数
|
|
366
|
+
delete_count = total_count - self.max_history
|
|
367
|
+
|
|
368
|
+
# 获取要删除的记录ID
|
|
369
|
+
cursor.execute(
|
|
370
|
+
"SELECT change_id FROM changes ORDER BY timestamp ASC LIMIT ?",
|
|
371
|
+
(delete_count,)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
change_ids = [row[0] for row in cursor.fetchall()]
|
|
375
|
+
|
|
376
|
+
# 删除记录
|
|
377
|
+
for change_id in change_ids:
|
|
378
|
+
self.delete_change(change_id)
|
|
379
|
+
|
|
380
|
+
logger.debug(f"已清理 {len(change_ids)} 条过旧的变更记录")
|
|
381
|
+
|
|
382
|
+
except Exception as e:
|
|
383
|
+
logger.error(f"清理过旧的历史记录失败: {str(e)}")
|