pomera-ai-commander 0.1.0 → 1.2.1
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.
- package/LICENSE +21 -21
- package/README.md +105 -680
- package/bin/pomera-ai-commander.js +62 -62
- package/core/__init__.py +65 -65
- package/core/app_context.py +482 -482
- package/core/async_text_processor.py +421 -421
- package/core/backup_manager.py +655 -655
- package/core/backup_recovery_manager.py +1033 -1033
- package/core/content_hash_cache.py +508 -508
- package/core/context_menu.py +313 -313
- package/core/data_validator.py +1066 -1066
- package/core/database_connection_manager.py +744 -744
- package/core/database_curl_settings_manager.py +608 -608
- package/core/database_promera_ai_settings_manager.py +446 -446
- package/core/database_schema.py +411 -411
- package/core/database_schema_manager.py +395 -395
- package/core/database_settings_manager.py +1507 -1507
- package/core/database_settings_manager_interface.py +456 -456
- package/core/dialog_manager.py +734 -734
- package/core/efficient_line_numbers.py +510 -510
- package/core/error_handler.py +746 -746
- package/core/error_service.py +431 -431
- package/core/event_consolidator.py +511 -511
- package/core/mcp/__init__.py +43 -43
- package/core/mcp/protocol.py +288 -288
- package/core/mcp/schema.py +251 -251
- package/core/mcp/server_stdio.py +299 -299
- package/core/mcp/tool_registry.py +2372 -2345
- package/core/memory_efficient_text_widget.py +711 -711
- package/core/migration_manager.py +914 -914
- package/core/migration_test_suite.py +1085 -1085
- package/core/migration_validator.py +1143 -1143
- package/core/optimized_find_replace.py +714 -714
- package/core/optimized_pattern_engine.py +424 -424
- package/core/optimized_search_highlighter.py +552 -552
- package/core/performance_monitor.py +674 -674
- package/core/persistence_manager.py +712 -712
- package/core/progressive_stats_calculator.py +632 -632
- package/core/regex_pattern_cache.py +529 -529
- package/core/regex_pattern_library.py +350 -350
- package/core/search_operation_manager.py +434 -434
- package/core/settings_defaults_registry.py +1087 -1087
- package/core/settings_integrity_validator.py +1111 -1111
- package/core/settings_serializer.py +557 -557
- package/core/settings_validator.py +1823 -1823
- package/core/smart_stats_calculator.py +709 -709
- package/core/statistics_update_manager.py +619 -619
- package/core/stats_config_manager.py +858 -858
- package/core/streaming_text_handler.py +723 -723
- package/core/task_scheduler.py +596 -596
- package/core/update_pattern_library.py +168 -168
- package/core/visibility_monitor.py +596 -596
- package/core/widget_cache.py +498 -498
- package/mcp.json +51 -61
- package/package.json +61 -57
- package/pomera.py +7482 -7482
- package/pomera_mcp_server.py +183 -144
- package/requirements.txt +32 -0
- package/tools/__init__.py +4 -4
- package/tools/ai_tools.py +2891 -2891
- package/tools/ascii_art_generator.py +352 -352
- package/tools/base64_tools.py +183 -183
- package/tools/base_tool.py +511 -511
- package/tools/case_tool.py +308 -308
- package/tools/column_tools.py +395 -395
- package/tools/cron_tool.py +884 -884
- package/tools/curl_history.py +600 -600
- package/tools/curl_processor.py +1207 -1207
- package/tools/curl_settings.py +502 -502
- package/tools/curl_tool.py +5467 -5467
- package/tools/diff_viewer.py +1071 -1071
- package/tools/email_extraction_tool.py +248 -248
- package/tools/email_header_analyzer.py +425 -425
- package/tools/extraction_tools.py +250 -250
- package/tools/find_replace.py +1750 -1750
- package/tools/folder_file_reporter.py +1463 -1463
- package/tools/folder_file_reporter_adapter.py +480 -480
- package/tools/generator_tools.py +1216 -1216
- package/tools/hash_generator.py +255 -255
- package/tools/html_tool.py +656 -656
- package/tools/jsonxml_tool.py +729 -729
- package/tools/line_tools.py +419 -419
- package/tools/markdown_tools.py +561 -561
- package/tools/mcp_widget.py +1417 -1417
- package/tools/notes_widget.py +973 -973
- package/tools/number_base_converter.py +372 -372
- package/tools/regex_extractor.py +571 -571
- package/tools/slug_generator.py +310 -310
- package/tools/sorter_tools.py +458 -458
- package/tools/string_escape_tool.py +392 -392
- package/tools/text_statistics_tool.py +365 -365
- package/tools/text_wrapper.py +430 -430
- package/tools/timestamp_converter.py +421 -421
- package/tools/tool_loader.py +710 -710
- package/tools/translator_tools.py +522 -522
- package/tools/url_link_extractor.py +261 -261
- package/tools/url_parser.py +204 -204
- package/tools/whitespace_tools.py +355 -355
- package/tools/word_frequency_counter.py +146 -146
- package/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/__pycache__/app_context.cpython-313.pyc +0 -0
- package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
- package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
- package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
- package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/error_service.cpython-313.pyc +0 -0
- package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
- package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
- package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
- package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
- package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
- package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
- package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
- package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
- package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
- package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
- package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
- package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
- package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
package/tools/curl_history.py
CHANGED
|
@@ -1,601 +1,601 @@
|
|
|
1
|
-
"""
|
|
2
|
-
cURL Tool History Management Module
|
|
3
|
-
|
|
4
|
-
This module provides request history management for the cURL GUI Tool.
|
|
5
|
-
It handles history storage, persistence, and organization functionality.
|
|
6
|
-
|
|
7
|
-
Author: Pomera AI Commander
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import os
|
|
12
|
-
from typing import Dict, Any, Optional, List
|
|
13
|
-
import logging
|
|
14
|
-
from datetime import datetime, timedelta
|
|
15
|
-
from dataclasses import dataclass, asdict
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass
|
|
19
|
-
class RequestHistoryItem:
|
|
20
|
-
"""Individual request history entry."""
|
|
21
|
-
timestamp: str
|
|
22
|
-
method: str
|
|
23
|
-
url: str
|
|
24
|
-
status_code: Optional[int]
|
|
25
|
-
response_time: Optional[float]
|
|
26
|
-
success: bool
|
|
27
|
-
headers: Dict[str, str]
|
|
28
|
-
body: Optional[str]
|
|
29
|
-
auth_type: str
|
|
30
|
-
response_preview: str # First 200 chars of response
|
|
31
|
-
response_size: Optional[int]
|
|
32
|
-
content_type: Optional[str]
|
|
33
|
-
|
|
34
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
35
|
-
"""Convert to dictionary for JSON serialization."""
|
|
36
|
-
return asdict(self)
|
|
37
|
-
|
|
38
|
-
@classmethod
|
|
39
|
-
def from_dict(cls, data: Dict[str, Any]) -> 'RequestHistoryItem':
|
|
40
|
-
"""Create from dictionary."""
|
|
41
|
-
return cls(**data)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class CurlHistoryManager:
|
|
45
|
-
"""
|
|
46
|
-
Manages request history persistence and organization for the cURL Tool.
|
|
47
|
-
|
|
48
|
-
Handles:
|
|
49
|
-
- History file storage and loading
|
|
50
|
-
- History item management (add, remove, search)
|
|
51
|
-
- History cleanup and organization
|
|
52
|
-
- Collections support
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
def __init__(self, history_file: str = "settings.json",
|
|
56
|
-
max_items: int = 100, logger=None):
|
|
57
|
-
"""
|
|
58
|
-
Initialize the history manager.
|
|
59
|
-
|
|
60
|
-
Args:
|
|
61
|
-
history_file: Path to the main settings file
|
|
62
|
-
max_items: Maximum number of history items to keep
|
|
63
|
-
logger: Logger instance for debugging
|
|
64
|
-
"""
|
|
65
|
-
self.history_file = history_file
|
|
66
|
-
self.max_items = max_items
|
|
67
|
-
self.logger = logger or logging.getLogger(__name__)
|
|
68
|
-
self.tool_key = "cURL Tool" # Key in tool_settings section
|
|
69
|
-
|
|
70
|
-
# History storage
|
|
71
|
-
self.history: List[RequestHistoryItem] = []
|
|
72
|
-
self.collections: Dict[str, List[str]] = {} # Collection name -> list of history item IDs
|
|
73
|
-
|
|
74
|
-
# Load history on initialization
|
|
75
|
-
self.load_history()
|
|
76
|
-
|
|
77
|
-
def add_request(self, method: str, url: str, headers: Dict[str, str] = None,
|
|
78
|
-
body: str = None, auth_type: str = "None",
|
|
79
|
-
status_code: int = None, response_time: float = None,
|
|
80
|
-
success: bool = True, response_body: str = "",
|
|
81
|
-
response_size: int = None, content_type: str = None) -> str:
|
|
82
|
-
"""
|
|
83
|
-
Add a request to history.
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
method: HTTP method
|
|
87
|
-
url: Request URL
|
|
88
|
-
headers: Request headers
|
|
89
|
-
body: Request body
|
|
90
|
-
auth_type: Authentication type used
|
|
91
|
-
status_code: Response status code
|
|
92
|
-
response_time: Response time in seconds
|
|
93
|
-
success: Whether request was successful
|
|
94
|
-
response_body: Response body content
|
|
95
|
-
response_size: Response size in bytes
|
|
96
|
-
content_type: Response content type
|
|
97
|
-
|
|
98
|
-
Returns:
|
|
99
|
-
History item ID (timestamp)
|
|
100
|
-
"""
|
|
101
|
-
timestamp = datetime.now().isoformat()
|
|
102
|
-
|
|
103
|
-
# Create response preview (first 200 chars)
|
|
104
|
-
response_preview = ""
|
|
105
|
-
if response_body:
|
|
106
|
-
response_preview = response_body[:200]
|
|
107
|
-
if len(response_body) > 200:
|
|
108
|
-
response_preview += "..."
|
|
109
|
-
|
|
110
|
-
# Create history item
|
|
111
|
-
history_item = RequestHistoryItem(
|
|
112
|
-
timestamp=timestamp,
|
|
113
|
-
method=method,
|
|
114
|
-
url=url,
|
|
115
|
-
status_code=status_code,
|
|
116
|
-
response_time=response_time,
|
|
117
|
-
success=success,
|
|
118
|
-
headers=headers or {},
|
|
119
|
-
body=body,
|
|
120
|
-
auth_type=auth_type,
|
|
121
|
-
response_preview=response_preview,
|
|
122
|
-
response_size=response_size,
|
|
123
|
-
content_type=content_type
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
# Add to history
|
|
127
|
-
self.history.append(history_item)
|
|
128
|
-
|
|
129
|
-
# Maintain max items limit
|
|
130
|
-
if len(self.history) > self.max_items:
|
|
131
|
-
removed_item = self.history.pop(0)
|
|
132
|
-
# Remove from collections if present
|
|
133
|
-
self._remove_from_collections(removed_item.timestamp)
|
|
134
|
-
|
|
135
|
-
# Save history
|
|
136
|
-
self.save_history()
|
|
137
|
-
|
|
138
|
-
self.logger.debug(f"Added request to history: {method} {url}")
|
|
139
|
-
return timestamp
|
|
140
|
-
|
|
141
|
-
def get_history(self, limit: int = None) -> List[RequestHistoryItem]:
|
|
142
|
-
"""
|
|
143
|
-
Get history items.
|
|
144
|
-
|
|
145
|
-
Args:
|
|
146
|
-
limit: Maximum number of items to return
|
|
147
|
-
|
|
148
|
-
Returns:
|
|
149
|
-
List of history items (newest first)
|
|
150
|
-
"""
|
|
151
|
-
history = sorted(self.history, key=lambda x: x.timestamp, reverse=True)
|
|
152
|
-
if limit:
|
|
153
|
-
return history[:limit]
|
|
154
|
-
return history
|
|
155
|
-
|
|
156
|
-
def get_history_item(self, timestamp: str) -> Optional[RequestHistoryItem]:
|
|
157
|
-
"""
|
|
158
|
-
Get a specific history item by timestamp.
|
|
159
|
-
|
|
160
|
-
Args:
|
|
161
|
-
timestamp: Item timestamp ID
|
|
162
|
-
|
|
163
|
-
Returns:
|
|
164
|
-
History item or None if not found
|
|
165
|
-
"""
|
|
166
|
-
for item in self.history:
|
|
167
|
-
if item.timestamp == timestamp:
|
|
168
|
-
return item
|
|
169
|
-
return None
|
|
170
|
-
|
|
171
|
-
def remove_history_item(self, timestamp: str) -> bool:
|
|
172
|
-
"""
|
|
173
|
-
Remove a history item.
|
|
174
|
-
|
|
175
|
-
Args:
|
|
176
|
-
timestamp: Item timestamp ID
|
|
177
|
-
|
|
178
|
-
Returns:
|
|
179
|
-
True if removed, False if not found
|
|
180
|
-
"""
|
|
181
|
-
for i, item in enumerate(self.history):
|
|
182
|
-
if item.timestamp == timestamp:
|
|
183
|
-
self.history.pop(i)
|
|
184
|
-
self._remove_from_collections(timestamp)
|
|
185
|
-
self.save_history()
|
|
186
|
-
self.logger.debug(f"Removed history item: {timestamp}")
|
|
187
|
-
return True
|
|
188
|
-
return False
|
|
189
|
-
|
|
190
|
-
def clear_history(self) -> bool:
|
|
191
|
-
"""
|
|
192
|
-
Clear all history.
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
True if successful
|
|
196
|
-
"""
|
|
197
|
-
try:
|
|
198
|
-
self.history.clear()
|
|
199
|
-
self.collections.clear()
|
|
200
|
-
self.save_history()
|
|
201
|
-
self.logger.info("History cleared")
|
|
202
|
-
return True
|
|
203
|
-
except Exception as e:
|
|
204
|
-
self.logger.error(f"Error clearing history: {e}")
|
|
205
|
-
return False
|
|
206
|
-
|
|
207
|
-
def search_history(self, query: str, field: str = "all") -> List[RequestHistoryItem]:
|
|
208
|
-
"""
|
|
209
|
-
Search history items.
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
query: Search query
|
|
213
|
-
field: Field to search in ("all", "url", "method", "status")
|
|
214
|
-
|
|
215
|
-
Returns:
|
|
216
|
-
List of matching history items
|
|
217
|
-
"""
|
|
218
|
-
query = query.lower()
|
|
219
|
-
results = []
|
|
220
|
-
|
|
221
|
-
for item in self.history:
|
|
222
|
-
match = False
|
|
223
|
-
|
|
224
|
-
if field == "all":
|
|
225
|
-
# Search in multiple fields
|
|
226
|
-
search_text = f"{item.method} {item.url} {item.status_code or ''} {item.response_preview}".lower()
|
|
227
|
-
match = query in search_text
|
|
228
|
-
elif field == "url":
|
|
229
|
-
match = query in item.url.lower()
|
|
230
|
-
elif field == "method":
|
|
231
|
-
match = query in item.method.lower()
|
|
232
|
-
elif field == "status":
|
|
233
|
-
match = query in str(item.status_code or "")
|
|
234
|
-
|
|
235
|
-
if match:
|
|
236
|
-
results.append(item)
|
|
237
|
-
|
|
238
|
-
return sorted(results, key=lambda x: x.timestamp, reverse=True)
|
|
239
|
-
|
|
240
|
-
def cleanup_old_history(self, retention_days: int = 30) -> int:
|
|
241
|
-
"""
|
|
242
|
-
Clean up old history items.
|
|
243
|
-
|
|
244
|
-
Args:
|
|
245
|
-
retention_days: Number of days to retain history
|
|
246
|
-
|
|
247
|
-
Returns:
|
|
248
|
-
Number of items removed
|
|
249
|
-
"""
|
|
250
|
-
try:
|
|
251
|
-
cutoff_date = datetime.now() - timedelta(days=retention_days)
|
|
252
|
-
initial_count = len(self.history)
|
|
253
|
-
|
|
254
|
-
# Filter out old items
|
|
255
|
-
self.history = [
|
|
256
|
-
item for item in self.history
|
|
257
|
-
if datetime.fromisoformat(item.timestamp) > cutoff_date
|
|
258
|
-
]
|
|
259
|
-
|
|
260
|
-
removed_count = initial_count - len(self.history)
|
|
261
|
-
|
|
262
|
-
if removed_count > 0:
|
|
263
|
-
# Clean up collections
|
|
264
|
-
self._cleanup_collections()
|
|
265
|
-
self.save_history()
|
|
266
|
-
self.logger.info(f"Cleaned up {removed_count} old history items")
|
|
267
|
-
|
|
268
|
-
return removed_count
|
|
269
|
-
|
|
270
|
-
except Exception as e:
|
|
271
|
-
self.logger.error(f"Error cleaning up history: {e}")
|
|
272
|
-
return 0
|
|
273
|
-
|
|
274
|
-
def create_collection(self, name: str, item_timestamps: List[str] = None) -> bool:
|
|
275
|
-
"""
|
|
276
|
-
Create a new collection.
|
|
277
|
-
|
|
278
|
-
Args:
|
|
279
|
-
name: Collection name
|
|
280
|
-
item_timestamps: List of history item timestamps to include
|
|
281
|
-
|
|
282
|
-
Returns:
|
|
283
|
-
True if successful
|
|
284
|
-
"""
|
|
285
|
-
try:
|
|
286
|
-
if name in self.collections:
|
|
287
|
-
self.logger.warning(f"Collection '{name}' already exists")
|
|
288
|
-
return False
|
|
289
|
-
|
|
290
|
-
self.collections[name] = item_timestamps or []
|
|
291
|
-
self.save_history()
|
|
292
|
-
self.logger.info(f"Created collection: {name}")
|
|
293
|
-
return True
|
|
294
|
-
|
|
295
|
-
except Exception as e:
|
|
296
|
-
self.logger.error(f"Error creating collection: {e}")
|
|
297
|
-
return False
|
|
298
|
-
|
|
299
|
-
def add_to_collection(self, collection_name: str, timestamp: str) -> bool:
|
|
300
|
-
"""
|
|
301
|
-
Add a history item to a collection.
|
|
302
|
-
|
|
303
|
-
Args:
|
|
304
|
-
collection_name: Name of the collection
|
|
305
|
-
timestamp: History item timestamp
|
|
306
|
-
|
|
307
|
-
Returns:
|
|
308
|
-
True if successful
|
|
309
|
-
"""
|
|
310
|
-
try:
|
|
311
|
-
if collection_name not in self.collections:
|
|
312
|
-
self.collections[collection_name] = []
|
|
313
|
-
|
|
314
|
-
if timestamp not in self.collections[collection_name]:
|
|
315
|
-
# Verify the history item exists
|
|
316
|
-
if self.get_history_item(timestamp):
|
|
317
|
-
self.collections[collection_name].append(timestamp)
|
|
318
|
-
self.save_history()
|
|
319
|
-
self.logger.debug(f"Added item {timestamp} to collection {collection_name}")
|
|
320
|
-
return True
|
|
321
|
-
else:
|
|
322
|
-
self.logger.warning(f"History item {timestamp} not found")
|
|
323
|
-
return False
|
|
324
|
-
else:
|
|
325
|
-
self.logger.debug(f"Item {timestamp} already in collection {collection_name}")
|
|
326
|
-
return True
|
|
327
|
-
|
|
328
|
-
except Exception as e:
|
|
329
|
-
self.logger.error(f"Error adding to collection: {e}")
|
|
330
|
-
return False
|
|
331
|
-
|
|
332
|
-
def remove_from_collection(self, collection_name: str, timestamp: str) -> bool:
|
|
333
|
-
"""
|
|
334
|
-
Remove a history item from a collection.
|
|
335
|
-
|
|
336
|
-
Args:
|
|
337
|
-
collection_name: Name of the collection
|
|
338
|
-
timestamp: History item timestamp
|
|
339
|
-
|
|
340
|
-
Returns:
|
|
341
|
-
True if successful
|
|
342
|
-
"""
|
|
343
|
-
try:
|
|
344
|
-
if collection_name in self.collections:
|
|
345
|
-
if timestamp in self.collections[collection_name]:
|
|
346
|
-
self.collections[collection_name].remove(timestamp)
|
|
347
|
-
self.save_history()
|
|
348
|
-
self.logger.debug(f"Removed item {timestamp} from collection {collection_name}")
|
|
349
|
-
return True
|
|
350
|
-
return False
|
|
351
|
-
|
|
352
|
-
except Exception as e:
|
|
353
|
-
self.logger.error(f"Error removing from collection: {e}")
|
|
354
|
-
return False
|
|
355
|
-
|
|
356
|
-
def get_collection(self, name: str) -> List[RequestHistoryItem]:
|
|
357
|
-
"""
|
|
358
|
-
Get items in a collection.
|
|
359
|
-
|
|
360
|
-
Args:
|
|
361
|
-
name: Collection name
|
|
362
|
-
|
|
363
|
-
Returns:
|
|
364
|
-
List of history items in the collection
|
|
365
|
-
"""
|
|
366
|
-
if name not in self.collections:
|
|
367
|
-
return []
|
|
368
|
-
|
|
369
|
-
items = []
|
|
370
|
-
for timestamp in self.collections[name]:
|
|
371
|
-
item = self.get_history_item(timestamp)
|
|
372
|
-
if item:
|
|
373
|
-
items.append(item)
|
|
374
|
-
|
|
375
|
-
return sorted(items, key=lambda x: x.timestamp, reverse=True)
|
|
376
|
-
|
|
377
|
-
def get_collections(self) -> Dict[str, int]:
|
|
378
|
-
"""
|
|
379
|
-
Get all collections with item counts.
|
|
380
|
-
|
|
381
|
-
Returns:
|
|
382
|
-
Dictionary of collection name -> item count
|
|
383
|
-
"""
|
|
384
|
-
result = {}
|
|
385
|
-
for name, timestamps in self.collections.items():
|
|
386
|
-
# Count only existing items
|
|
387
|
-
count = sum(1 for ts in timestamps if self.get_history_item(ts))
|
|
388
|
-
result[name] = count
|
|
389
|
-
return result
|
|
390
|
-
|
|
391
|
-
def delete_collection(self, name: str) -> bool:
|
|
392
|
-
"""
|
|
393
|
-
Delete a collection.
|
|
394
|
-
|
|
395
|
-
Args:
|
|
396
|
-
name: Collection name
|
|
397
|
-
|
|
398
|
-
Returns:
|
|
399
|
-
True if successful
|
|
400
|
-
"""
|
|
401
|
-
try:
|
|
402
|
-
if name in self.collections:
|
|
403
|
-
del self.collections[name]
|
|
404
|
-
self.save_history()
|
|
405
|
-
self.logger.info(f"Deleted collection: {name}")
|
|
406
|
-
return True
|
|
407
|
-
return False
|
|
408
|
-
|
|
409
|
-
except Exception as e:
|
|
410
|
-
self.logger.error(f"Error deleting collection: {e}")
|
|
411
|
-
return False
|
|
412
|
-
|
|
413
|
-
def export_history(self, filepath: str, collection_name: str = None) -> bool:
|
|
414
|
-
"""
|
|
415
|
-
Export history to a file.
|
|
416
|
-
|
|
417
|
-
Args:
|
|
418
|
-
filepath: Export file path
|
|
419
|
-
collection_name: Optional collection name to export
|
|
420
|
-
|
|
421
|
-
Returns:
|
|
422
|
-
True if successful
|
|
423
|
-
"""
|
|
424
|
-
try:
|
|
425
|
-
if collection_name:
|
|
426
|
-
items = self.get_collection(collection_name)
|
|
427
|
-
export_data = {
|
|
428
|
-
"type": "collection",
|
|
429
|
-
"name": collection_name,
|
|
430
|
-
"items": [item.to_dict() for item in items]
|
|
431
|
-
}
|
|
432
|
-
else:
|
|
433
|
-
export_data = {
|
|
434
|
-
"type": "full_history",
|
|
435
|
-
"items": [item.to_dict() for item in self.history],
|
|
436
|
-
"collections": self.collections
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
export_data["export_date"] = datetime.now().isoformat()
|
|
440
|
-
export_data["version"] = "1.0"
|
|
441
|
-
|
|
442
|
-
with open(filepath, 'w', encoding='utf-8') as f:
|
|
443
|
-
json.dump(export_data, f, indent=2, ensure_ascii=False)
|
|
444
|
-
|
|
445
|
-
self.logger.info(f"History exported to {filepath}")
|
|
446
|
-
return True
|
|
447
|
-
|
|
448
|
-
except Exception as e:
|
|
449
|
-
self.logger.error(f"Error exporting history: {e}")
|
|
450
|
-
return False
|
|
451
|
-
|
|
452
|
-
def import_history(self, filepath: str, merge: bool = True) -> bool:
|
|
453
|
-
"""
|
|
454
|
-
Import history from a file.
|
|
455
|
-
|
|
456
|
-
Args:
|
|
457
|
-
filepath: Import file path
|
|
458
|
-
merge: Whether to merge with existing history
|
|
459
|
-
|
|
460
|
-
Returns:
|
|
461
|
-
True if successful
|
|
462
|
-
"""
|
|
463
|
-
try:
|
|
464
|
-
with open(filepath, 'r', encoding='utf-8') as f:
|
|
465
|
-
import_data = json.load(f)
|
|
466
|
-
|
|
467
|
-
if not merge:
|
|
468
|
-
self.history.clear()
|
|
469
|
-
self.collections.clear()
|
|
470
|
-
|
|
471
|
-
# Import items
|
|
472
|
-
items_data = import_data.get("items", [])
|
|
473
|
-
for item_data in items_data:
|
|
474
|
-
try:
|
|
475
|
-
item = RequestHistoryItem.from_dict(item_data)
|
|
476
|
-
# Check if item already exists (by timestamp)
|
|
477
|
-
if not any(h.timestamp == item.timestamp for h in self.history):
|
|
478
|
-
self.history.append(item)
|
|
479
|
-
except Exception as e:
|
|
480
|
-
self.logger.warning(f"Error importing history item: {e}")
|
|
481
|
-
|
|
482
|
-
# Import collections if present
|
|
483
|
-
if "collections" in import_data:
|
|
484
|
-
for name, timestamps in import_data["collections"].items():
|
|
485
|
-
if name not in self.collections:
|
|
486
|
-
self.collections[name] = []
|
|
487
|
-
# Add timestamps that don't already exist
|
|
488
|
-
for ts in timestamps:
|
|
489
|
-
if ts not in self.collections[name]:
|
|
490
|
-
self.collections[name].append(ts)
|
|
491
|
-
|
|
492
|
-
# Maintain max items limit
|
|
493
|
-
if len(self.history) > self.max_items:
|
|
494
|
-
# Sort by timestamp and keep newest
|
|
495
|
-
self.history.sort(key=lambda x: x.timestamp, reverse=True)
|
|
496
|
-
self.history = self.history[:self.max_items]
|
|
497
|
-
|
|
498
|
-
self.save_history()
|
|
499
|
-
self.logger.info(f"History imported from {filepath}")
|
|
500
|
-
return True
|
|
501
|
-
|
|
502
|
-
except Exception as e:
|
|
503
|
-
self.logger.error(f"Error importing history: {e}")
|
|
504
|
-
return False
|
|
505
|
-
|
|
506
|
-
def load_history(self) -> bool:
|
|
507
|
-
"""
|
|
508
|
-
Load history from centralized settings.json file.
|
|
509
|
-
|
|
510
|
-
Returns:
|
|
511
|
-
True if successful
|
|
512
|
-
"""
|
|
513
|
-
try:
|
|
514
|
-
if os.path.exists(self.history_file):
|
|
515
|
-
with open(self.history_file, 'r', encoding='utf-8') as f:
|
|
516
|
-
all_settings = json.load(f)
|
|
517
|
-
|
|
518
|
-
# Get tool settings from tool_settings section
|
|
519
|
-
tool_settings = all_settings.get("tool_settings", {})
|
|
520
|
-
curl_settings = tool_settings.get(self.tool_key, {})
|
|
521
|
-
|
|
522
|
-
# Load history items
|
|
523
|
-
self.history = []
|
|
524
|
-
for item_data in curl_settings.get("history", []):
|
|
525
|
-
try:
|
|
526
|
-
item = RequestHistoryItem.from_dict(item_data)
|
|
527
|
-
self.history.append(item)
|
|
528
|
-
except Exception as e:
|
|
529
|
-
self.logger.warning(f"Error loading history item: {e}")
|
|
530
|
-
|
|
531
|
-
# Load collections
|
|
532
|
-
self.collections = curl_settings.get("collections", {})
|
|
533
|
-
|
|
534
|
-
self.logger.info(f"Loaded {len(self.history)} history items from {self.history_file}")
|
|
535
|
-
return True
|
|
536
|
-
else:
|
|
537
|
-
self.logger.info("No history file found, starting with empty history")
|
|
538
|
-
return True
|
|
539
|
-
|
|
540
|
-
except Exception as e:
|
|
541
|
-
self.logger.error(f"Error loading history: {e}")
|
|
542
|
-
return False
|
|
543
|
-
|
|
544
|
-
def save_history(self) -> bool:
|
|
545
|
-
"""
|
|
546
|
-
Save history to centralized settings.json file.
|
|
547
|
-
|
|
548
|
-
Returns:
|
|
549
|
-
True if successful
|
|
550
|
-
"""
|
|
551
|
-
try:
|
|
552
|
-
# Load existing settings file
|
|
553
|
-
all_settings = {}
|
|
554
|
-
if os.path.exists(self.history_file):
|
|
555
|
-
with open(self.history_file, 'r', encoding='utf-8') as f:
|
|
556
|
-
all_settings = json.load(f)
|
|
557
|
-
|
|
558
|
-
# Ensure tool_settings section exists
|
|
559
|
-
if "tool_settings" not in all_settings:
|
|
560
|
-
all_settings["tool_settings"] = {}
|
|
561
|
-
|
|
562
|
-
# Ensure cURL Tool section exists
|
|
563
|
-
if self.tool_key not in all_settings["tool_settings"]:
|
|
564
|
-
all_settings["tool_settings"][self.tool_key] = {}
|
|
565
|
-
|
|
566
|
-
# Update history and collections in cURL Tool settings
|
|
567
|
-
all_settings["tool_settings"][self.tool_key]["history"] = [item.to_dict() for item in self.history]
|
|
568
|
-
all_settings["tool_settings"][self.tool_key]["collections"] = self.collections
|
|
569
|
-
all_settings["tool_settings"][self.tool_key]["history_last_updated"] = datetime.now().isoformat()
|
|
570
|
-
all_settings["tool_settings"][self.tool_key]["history_version"] = "1.0"
|
|
571
|
-
|
|
572
|
-
# Write settings to file
|
|
573
|
-
with open(self.history_file, 'w', encoding='utf-8') as f:
|
|
574
|
-
json.dump(all_settings, f, indent=4, ensure_ascii=False)
|
|
575
|
-
|
|
576
|
-
self.logger.debug(f"History saved to {self.history_file}")
|
|
577
|
-
return True
|
|
578
|
-
|
|
579
|
-
except Exception as e:
|
|
580
|
-
self.logger.error(f"Error saving history: {e}")
|
|
581
|
-
return False
|
|
582
|
-
|
|
583
|
-
def _remove_from_collections(self, timestamp: str):
|
|
584
|
-
"""Remove a timestamp from all collections."""
|
|
585
|
-
for collection_name in list(self.collections.keys()):
|
|
586
|
-
if timestamp in self.collections[collection_name]:
|
|
587
|
-
self.collections[collection_name].remove(timestamp)
|
|
588
|
-
|
|
589
|
-
def _cleanup_collections(self):
|
|
590
|
-
"""Clean up collections by removing references to non-existent items."""
|
|
591
|
-
for collection_name in list(self.collections.keys()):
|
|
592
|
-
# Filter out timestamps that don't exist in history
|
|
593
|
-
valid_timestamps = [
|
|
594
|
-
ts for ts in self.collections[collection_name]
|
|
595
|
-
if self.get_history_item(ts) is not None
|
|
596
|
-
]
|
|
597
|
-
self.collections[collection_name] = valid_timestamps
|
|
598
|
-
|
|
599
|
-
# Remove empty collections
|
|
600
|
-
if not self.collections[collection_name]:
|
|
1
|
+
"""
|
|
2
|
+
cURL Tool History Management Module
|
|
3
|
+
|
|
4
|
+
This module provides request history management for the cURL GUI Tool.
|
|
5
|
+
It handles history storage, persistence, and organization functionality.
|
|
6
|
+
|
|
7
|
+
Author: Pomera AI Commander
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from typing import Dict, Any, Optional, List
|
|
13
|
+
import logging
|
|
14
|
+
from datetime import datetime, timedelta
|
|
15
|
+
from dataclasses import dataclass, asdict
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class RequestHistoryItem:
|
|
20
|
+
"""Individual request history entry."""
|
|
21
|
+
timestamp: str
|
|
22
|
+
method: str
|
|
23
|
+
url: str
|
|
24
|
+
status_code: Optional[int]
|
|
25
|
+
response_time: Optional[float]
|
|
26
|
+
success: bool
|
|
27
|
+
headers: Dict[str, str]
|
|
28
|
+
body: Optional[str]
|
|
29
|
+
auth_type: str
|
|
30
|
+
response_preview: str # First 200 chars of response
|
|
31
|
+
response_size: Optional[int]
|
|
32
|
+
content_type: Optional[str]
|
|
33
|
+
|
|
34
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
35
|
+
"""Convert to dictionary for JSON serialization."""
|
|
36
|
+
return asdict(self)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'RequestHistoryItem':
|
|
40
|
+
"""Create from dictionary."""
|
|
41
|
+
return cls(**data)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CurlHistoryManager:
|
|
45
|
+
"""
|
|
46
|
+
Manages request history persistence and organization for the cURL Tool.
|
|
47
|
+
|
|
48
|
+
Handles:
|
|
49
|
+
- History file storage and loading
|
|
50
|
+
- History item management (add, remove, search)
|
|
51
|
+
- History cleanup and organization
|
|
52
|
+
- Collections support
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, history_file: str = "settings.json",
|
|
56
|
+
max_items: int = 100, logger=None):
|
|
57
|
+
"""
|
|
58
|
+
Initialize the history manager.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
history_file: Path to the main settings file
|
|
62
|
+
max_items: Maximum number of history items to keep
|
|
63
|
+
logger: Logger instance for debugging
|
|
64
|
+
"""
|
|
65
|
+
self.history_file = history_file
|
|
66
|
+
self.max_items = max_items
|
|
67
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
68
|
+
self.tool_key = "cURL Tool" # Key in tool_settings section
|
|
69
|
+
|
|
70
|
+
# History storage
|
|
71
|
+
self.history: List[RequestHistoryItem] = []
|
|
72
|
+
self.collections: Dict[str, List[str]] = {} # Collection name -> list of history item IDs
|
|
73
|
+
|
|
74
|
+
# Load history on initialization
|
|
75
|
+
self.load_history()
|
|
76
|
+
|
|
77
|
+
def add_request(self, method: str, url: str, headers: Dict[str, str] = None,
|
|
78
|
+
body: str = None, auth_type: str = "None",
|
|
79
|
+
status_code: int = None, response_time: float = None,
|
|
80
|
+
success: bool = True, response_body: str = "",
|
|
81
|
+
response_size: int = None, content_type: str = None) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Add a request to history.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
method: HTTP method
|
|
87
|
+
url: Request URL
|
|
88
|
+
headers: Request headers
|
|
89
|
+
body: Request body
|
|
90
|
+
auth_type: Authentication type used
|
|
91
|
+
status_code: Response status code
|
|
92
|
+
response_time: Response time in seconds
|
|
93
|
+
success: Whether request was successful
|
|
94
|
+
response_body: Response body content
|
|
95
|
+
response_size: Response size in bytes
|
|
96
|
+
content_type: Response content type
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
History item ID (timestamp)
|
|
100
|
+
"""
|
|
101
|
+
timestamp = datetime.now().isoformat()
|
|
102
|
+
|
|
103
|
+
# Create response preview (first 200 chars)
|
|
104
|
+
response_preview = ""
|
|
105
|
+
if response_body:
|
|
106
|
+
response_preview = response_body[:200]
|
|
107
|
+
if len(response_body) > 200:
|
|
108
|
+
response_preview += "..."
|
|
109
|
+
|
|
110
|
+
# Create history item
|
|
111
|
+
history_item = RequestHistoryItem(
|
|
112
|
+
timestamp=timestamp,
|
|
113
|
+
method=method,
|
|
114
|
+
url=url,
|
|
115
|
+
status_code=status_code,
|
|
116
|
+
response_time=response_time,
|
|
117
|
+
success=success,
|
|
118
|
+
headers=headers or {},
|
|
119
|
+
body=body,
|
|
120
|
+
auth_type=auth_type,
|
|
121
|
+
response_preview=response_preview,
|
|
122
|
+
response_size=response_size,
|
|
123
|
+
content_type=content_type
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Add to history
|
|
127
|
+
self.history.append(history_item)
|
|
128
|
+
|
|
129
|
+
# Maintain max items limit
|
|
130
|
+
if len(self.history) > self.max_items:
|
|
131
|
+
removed_item = self.history.pop(0)
|
|
132
|
+
# Remove from collections if present
|
|
133
|
+
self._remove_from_collections(removed_item.timestamp)
|
|
134
|
+
|
|
135
|
+
# Save history
|
|
136
|
+
self.save_history()
|
|
137
|
+
|
|
138
|
+
self.logger.debug(f"Added request to history: {method} {url}")
|
|
139
|
+
return timestamp
|
|
140
|
+
|
|
141
|
+
def get_history(self, limit: int = None) -> List[RequestHistoryItem]:
|
|
142
|
+
"""
|
|
143
|
+
Get history items.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
limit: Maximum number of items to return
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of history items (newest first)
|
|
150
|
+
"""
|
|
151
|
+
history = sorted(self.history, key=lambda x: x.timestamp, reverse=True)
|
|
152
|
+
if limit:
|
|
153
|
+
return history[:limit]
|
|
154
|
+
return history
|
|
155
|
+
|
|
156
|
+
def get_history_item(self, timestamp: str) -> Optional[RequestHistoryItem]:
|
|
157
|
+
"""
|
|
158
|
+
Get a specific history item by timestamp.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
timestamp: Item timestamp ID
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
History item or None if not found
|
|
165
|
+
"""
|
|
166
|
+
for item in self.history:
|
|
167
|
+
if item.timestamp == timestamp:
|
|
168
|
+
return item
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def remove_history_item(self, timestamp: str) -> bool:
|
|
172
|
+
"""
|
|
173
|
+
Remove a history item.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
timestamp: Item timestamp ID
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
True if removed, False if not found
|
|
180
|
+
"""
|
|
181
|
+
for i, item in enumerate(self.history):
|
|
182
|
+
if item.timestamp == timestamp:
|
|
183
|
+
self.history.pop(i)
|
|
184
|
+
self._remove_from_collections(timestamp)
|
|
185
|
+
self.save_history()
|
|
186
|
+
self.logger.debug(f"Removed history item: {timestamp}")
|
|
187
|
+
return True
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
def clear_history(self) -> bool:
|
|
191
|
+
"""
|
|
192
|
+
Clear all history.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
True if successful
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
self.history.clear()
|
|
199
|
+
self.collections.clear()
|
|
200
|
+
self.save_history()
|
|
201
|
+
self.logger.info("History cleared")
|
|
202
|
+
return True
|
|
203
|
+
except Exception as e:
|
|
204
|
+
self.logger.error(f"Error clearing history: {e}")
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
def search_history(self, query: str, field: str = "all") -> List[RequestHistoryItem]:
|
|
208
|
+
"""
|
|
209
|
+
Search history items.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
query: Search query
|
|
213
|
+
field: Field to search in ("all", "url", "method", "status")
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
List of matching history items
|
|
217
|
+
"""
|
|
218
|
+
query = query.lower()
|
|
219
|
+
results = []
|
|
220
|
+
|
|
221
|
+
for item in self.history:
|
|
222
|
+
match = False
|
|
223
|
+
|
|
224
|
+
if field == "all":
|
|
225
|
+
# Search in multiple fields
|
|
226
|
+
search_text = f"{item.method} {item.url} {item.status_code or ''} {item.response_preview}".lower()
|
|
227
|
+
match = query in search_text
|
|
228
|
+
elif field == "url":
|
|
229
|
+
match = query in item.url.lower()
|
|
230
|
+
elif field == "method":
|
|
231
|
+
match = query in item.method.lower()
|
|
232
|
+
elif field == "status":
|
|
233
|
+
match = query in str(item.status_code or "")
|
|
234
|
+
|
|
235
|
+
if match:
|
|
236
|
+
results.append(item)
|
|
237
|
+
|
|
238
|
+
return sorted(results, key=lambda x: x.timestamp, reverse=True)
|
|
239
|
+
|
|
240
|
+
def cleanup_old_history(self, retention_days: int = 30) -> int:
|
|
241
|
+
"""
|
|
242
|
+
Clean up old history items.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
retention_days: Number of days to retain history
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Number of items removed
|
|
249
|
+
"""
|
|
250
|
+
try:
|
|
251
|
+
cutoff_date = datetime.now() - timedelta(days=retention_days)
|
|
252
|
+
initial_count = len(self.history)
|
|
253
|
+
|
|
254
|
+
# Filter out old items
|
|
255
|
+
self.history = [
|
|
256
|
+
item for item in self.history
|
|
257
|
+
if datetime.fromisoformat(item.timestamp) > cutoff_date
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
removed_count = initial_count - len(self.history)
|
|
261
|
+
|
|
262
|
+
if removed_count > 0:
|
|
263
|
+
# Clean up collections
|
|
264
|
+
self._cleanup_collections()
|
|
265
|
+
self.save_history()
|
|
266
|
+
self.logger.info(f"Cleaned up {removed_count} old history items")
|
|
267
|
+
|
|
268
|
+
return removed_count
|
|
269
|
+
|
|
270
|
+
except Exception as e:
|
|
271
|
+
self.logger.error(f"Error cleaning up history: {e}")
|
|
272
|
+
return 0
|
|
273
|
+
|
|
274
|
+
def create_collection(self, name: str, item_timestamps: List[str] = None) -> bool:
|
|
275
|
+
"""
|
|
276
|
+
Create a new collection.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
name: Collection name
|
|
280
|
+
item_timestamps: List of history item timestamps to include
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
True if successful
|
|
284
|
+
"""
|
|
285
|
+
try:
|
|
286
|
+
if name in self.collections:
|
|
287
|
+
self.logger.warning(f"Collection '{name}' already exists")
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
self.collections[name] = item_timestamps or []
|
|
291
|
+
self.save_history()
|
|
292
|
+
self.logger.info(f"Created collection: {name}")
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
self.logger.error(f"Error creating collection: {e}")
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
def add_to_collection(self, collection_name: str, timestamp: str) -> bool:
|
|
300
|
+
"""
|
|
301
|
+
Add a history item to a collection.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
collection_name: Name of the collection
|
|
305
|
+
timestamp: History item timestamp
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
True if successful
|
|
309
|
+
"""
|
|
310
|
+
try:
|
|
311
|
+
if collection_name not in self.collections:
|
|
312
|
+
self.collections[collection_name] = []
|
|
313
|
+
|
|
314
|
+
if timestamp not in self.collections[collection_name]:
|
|
315
|
+
# Verify the history item exists
|
|
316
|
+
if self.get_history_item(timestamp):
|
|
317
|
+
self.collections[collection_name].append(timestamp)
|
|
318
|
+
self.save_history()
|
|
319
|
+
self.logger.debug(f"Added item {timestamp} to collection {collection_name}")
|
|
320
|
+
return True
|
|
321
|
+
else:
|
|
322
|
+
self.logger.warning(f"History item {timestamp} not found")
|
|
323
|
+
return False
|
|
324
|
+
else:
|
|
325
|
+
self.logger.debug(f"Item {timestamp} already in collection {collection_name}")
|
|
326
|
+
return True
|
|
327
|
+
|
|
328
|
+
except Exception as e:
|
|
329
|
+
self.logger.error(f"Error adding to collection: {e}")
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
def remove_from_collection(self, collection_name: str, timestamp: str) -> bool:
|
|
333
|
+
"""
|
|
334
|
+
Remove a history item from a collection.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
collection_name: Name of the collection
|
|
338
|
+
timestamp: History item timestamp
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
True if successful
|
|
342
|
+
"""
|
|
343
|
+
try:
|
|
344
|
+
if collection_name in self.collections:
|
|
345
|
+
if timestamp in self.collections[collection_name]:
|
|
346
|
+
self.collections[collection_name].remove(timestamp)
|
|
347
|
+
self.save_history()
|
|
348
|
+
self.logger.debug(f"Removed item {timestamp} from collection {collection_name}")
|
|
349
|
+
return True
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
except Exception as e:
|
|
353
|
+
self.logger.error(f"Error removing from collection: {e}")
|
|
354
|
+
return False
|
|
355
|
+
|
|
356
|
+
def get_collection(self, name: str) -> List[RequestHistoryItem]:
|
|
357
|
+
"""
|
|
358
|
+
Get items in a collection.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
name: Collection name
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
List of history items in the collection
|
|
365
|
+
"""
|
|
366
|
+
if name not in self.collections:
|
|
367
|
+
return []
|
|
368
|
+
|
|
369
|
+
items = []
|
|
370
|
+
for timestamp in self.collections[name]:
|
|
371
|
+
item = self.get_history_item(timestamp)
|
|
372
|
+
if item:
|
|
373
|
+
items.append(item)
|
|
374
|
+
|
|
375
|
+
return sorted(items, key=lambda x: x.timestamp, reverse=True)
|
|
376
|
+
|
|
377
|
+
def get_collections(self) -> Dict[str, int]:
|
|
378
|
+
"""
|
|
379
|
+
Get all collections with item counts.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Dictionary of collection name -> item count
|
|
383
|
+
"""
|
|
384
|
+
result = {}
|
|
385
|
+
for name, timestamps in self.collections.items():
|
|
386
|
+
# Count only existing items
|
|
387
|
+
count = sum(1 for ts in timestamps if self.get_history_item(ts))
|
|
388
|
+
result[name] = count
|
|
389
|
+
return result
|
|
390
|
+
|
|
391
|
+
def delete_collection(self, name: str) -> bool:
|
|
392
|
+
"""
|
|
393
|
+
Delete a collection.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
name: Collection name
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
True if successful
|
|
400
|
+
"""
|
|
401
|
+
try:
|
|
402
|
+
if name in self.collections:
|
|
403
|
+
del self.collections[name]
|
|
404
|
+
self.save_history()
|
|
405
|
+
self.logger.info(f"Deleted collection: {name}")
|
|
406
|
+
return True
|
|
407
|
+
return False
|
|
408
|
+
|
|
409
|
+
except Exception as e:
|
|
410
|
+
self.logger.error(f"Error deleting collection: {e}")
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
def export_history(self, filepath: str, collection_name: str = None) -> bool:
|
|
414
|
+
"""
|
|
415
|
+
Export history to a file.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
filepath: Export file path
|
|
419
|
+
collection_name: Optional collection name to export
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
True if successful
|
|
423
|
+
"""
|
|
424
|
+
try:
|
|
425
|
+
if collection_name:
|
|
426
|
+
items = self.get_collection(collection_name)
|
|
427
|
+
export_data = {
|
|
428
|
+
"type": "collection",
|
|
429
|
+
"name": collection_name,
|
|
430
|
+
"items": [item.to_dict() for item in items]
|
|
431
|
+
}
|
|
432
|
+
else:
|
|
433
|
+
export_data = {
|
|
434
|
+
"type": "full_history",
|
|
435
|
+
"items": [item.to_dict() for item in self.history],
|
|
436
|
+
"collections": self.collections
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export_data["export_date"] = datetime.now().isoformat()
|
|
440
|
+
export_data["version"] = "1.0"
|
|
441
|
+
|
|
442
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
443
|
+
json.dump(export_data, f, indent=2, ensure_ascii=False)
|
|
444
|
+
|
|
445
|
+
self.logger.info(f"History exported to {filepath}")
|
|
446
|
+
return True
|
|
447
|
+
|
|
448
|
+
except Exception as e:
|
|
449
|
+
self.logger.error(f"Error exporting history: {e}")
|
|
450
|
+
return False
|
|
451
|
+
|
|
452
|
+
def import_history(self, filepath: str, merge: bool = True) -> bool:
|
|
453
|
+
"""
|
|
454
|
+
Import history from a file.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
filepath: Import file path
|
|
458
|
+
merge: Whether to merge with existing history
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
True if successful
|
|
462
|
+
"""
|
|
463
|
+
try:
|
|
464
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
465
|
+
import_data = json.load(f)
|
|
466
|
+
|
|
467
|
+
if not merge:
|
|
468
|
+
self.history.clear()
|
|
469
|
+
self.collections.clear()
|
|
470
|
+
|
|
471
|
+
# Import items
|
|
472
|
+
items_data = import_data.get("items", [])
|
|
473
|
+
for item_data in items_data:
|
|
474
|
+
try:
|
|
475
|
+
item = RequestHistoryItem.from_dict(item_data)
|
|
476
|
+
# Check if item already exists (by timestamp)
|
|
477
|
+
if not any(h.timestamp == item.timestamp for h in self.history):
|
|
478
|
+
self.history.append(item)
|
|
479
|
+
except Exception as e:
|
|
480
|
+
self.logger.warning(f"Error importing history item: {e}")
|
|
481
|
+
|
|
482
|
+
# Import collections if present
|
|
483
|
+
if "collections" in import_data:
|
|
484
|
+
for name, timestamps in import_data["collections"].items():
|
|
485
|
+
if name not in self.collections:
|
|
486
|
+
self.collections[name] = []
|
|
487
|
+
# Add timestamps that don't already exist
|
|
488
|
+
for ts in timestamps:
|
|
489
|
+
if ts not in self.collections[name]:
|
|
490
|
+
self.collections[name].append(ts)
|
|
491
|
+
|
|
492
|
+
# Maintain max items limit
|
|
493
|
+
if len(self.history) > self.max_items:
|
|
494
|
+
# Sort by timestamp and keep newest
|
|
495
|
+
self.history.sort(key=lambda x: x.timestamp, reverse=True)
|
|
496
|
+
self.history = self.history[:self.max_items]
|
|
497
|
+
|
|
498
|
+
self.save_history()
|
|
499
|
+
self.logger.info(f"History imported from {filepath}")
|
|
500
|
+
return True
|
|
501
|
+
|
|
502
|
+
except Exception as e:
|
|
503
|
+
self.logger.error(f"Error importing history: {e}")
|
|
504
|
+
return False
|
|
505
|
+
|
|
506
|
+
def load_history(self) -> bool:
|
|
507
|
+
"""
|
|
508
|
+
Load history from centralized settings.json file.
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
True if successful
|
|
512
|
+
"""
|
|
513
|
+
try:
|
|
514
|
+
if os.path.exists(self.history_file):
|
|
515
|
+
with open(self.history_file, 'r', encoding='utf-8') as f:
|
|
516
|
+
all_settings = json.load(f)
|
|
517
|
+
|
|
518
|
+
# Get tool settings from tool_settings section
|
|
519
|
+
tool_settings = all_settings.get("tool_settings", {})
|
|
520
|
+
curl_settings = tool_settings.get(self.tool_key, {})
|
|
521
|
+
|
|
522
|
+
# Load history items
|
|
523
|
+
self.history = []
|
|
524
|
+
for item_data in curl_settings.get("history", []):
|
|
525
|
+
try:
|
|
526
|
+
item = RequestHistoryItem.from_dict(item_data)
|
|
527
|
+
self.history.append(item)
|
|
528
|
+
except Exception as e:
|
|
529
|
+
self.logger.warning(f"Error loading history item: {e}")
|
|
530
|
+
|
|
531
|
+
# Load collections
|
|
532
|
+
self.collections = curl_settings.get("collections", {})
|
|
533
|
+
|
|
534
|
+
self.logger.info(f"Loaded {len(self.history)} history items from {self.history_file}")
|
|
535
|
+
return True
|
|
536
|
+
else:
|
|
537
|
+
self.logger.info("No history file found, starting with empty history")
|
|
538
|
+
return True
|
|
539
|
+
|
|
540
|
+
except Exception as e:
|
|
541
|
+
self.logger.error(f"Error loading history: {e}")
|
|
542
|
+
return False
|
|
543
|
+
|
|
544
|
+
def save_history(self) -> bool:
|
|
545
|
+
"""
|
|
546
|
+
Save history to centralized settings.json file.
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
True if successful
|
|
550
|
+
"""
|
|
551
|
+
try:
|
|
552
|
+
# Load existing settings file
|
|
553
|
+
all_settings = {}
|
|
554
|
+
if os.path.exists(self.history_file):
|
|
555
|
+
with open(self.history_file, 'r', encoding='utf-8') as f:
|
|
556
|
+
all_settings = json.load(f)
|
|
557
|
+
|
|
558
|
+
# Ensure tool_settings section exists
|
|
559
|
+
if "tool_settings" not in all_settings:
|
|
560
|
+
all_settings["tool_settings"] = {}
|
|
561
|
+
|
|
562
|
+
# Ensure cURL Tool section exists
|
|
563
|
+
if self.tool_key not in all_settings["tool_settings"]:
|
|
564
|
+
all_settings["tool_settings"][self.tool_key] = {}
|
|
565
|
+
|
|
566
|
+
# Update history and collections in cURL Tool settings
|
|
567
|
+
all_settings["tool_settings"][self.tool_key]["history"] = [item.to_dict() for item in self.history]
|
|
568
|
+
all_settings["tool_settings"][self.tool_key]["collections"] = self.collections
|
|
569
|
+
all_settings["tool_settings"][self.tool_key]["history_last_updated"] = datetime.now().isoformat()
|
|
570
|
+
all_settings["tool_settings"][self.tool_key]["history_version"] = "1.0"
|
|
571
|
+
|
|
572
|
+
# Write settings to file
|
|
573
|
+
with open(self.history_file, 'w', encoding='utf-8') as f:
|
|
574
|
+
json.dump(all_settings, f, indent=4, ensure_ascii=False)
|
|
575
|
+
|
|
576
|
+
self.logger.debug(f"History saved to {self.history_file}")
|
|
577
|
+
return True
|
|
578
|
+
|
|
579
|
+
except Exception as e:
|
|
580
|
+
self.logger.error(f"Error saving history: {e}")
|
|
581
|
+
return False
|
|
582
|
+
|
|
583
|
+
def _remove_from_collections(self, timestamp: str):
|
|
584
|
+
"""Remove a timestamp from all collections."""
|
|
585
|
+
for collection_name in list(self.collections.keys()):
|
|
586
|
+
if timestamp in self.collections[collection_name]:
|
|
587
|
+
self.collections[collection_name].remove(timestamp)
|
|
588
|
+
|
|
589
|
+
def _cleanup_collections(self):
|
|
590
|
+
"""Clean up collections by removing references to non-existent items."""
|
|
591
|
+
for collection_name in list(self.collections.keys()):
|
|
592
|
+
# Filter out timestamps that don't exist in history
|
|
593
|
+
valid_timestamps = [
|
|
594
|
+
ts for ts in self.collections[collection_name]
|
|
595
|
+
if self.get_history_item(ts) is not None
|
|
596
|
+
]
|
|
597
|
+
self.collections[collection_name] = valid_timestamps
|
|
598
|
+
|
|
599
|
+
# Remove empty collections
|
|
600
|
+
if not self.collections[collection_name]:
|
|
601
601
|
del self.collections[collection_name]
|