unrealon 1.1.1__py3-none-any.whl → 1.1.5__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.
- unrealon/__init__.py +16 -6
- unrealon-1.1.5.dist-info/METADATA +621 -0
- unrealon-1.1.5.dist-info/RECORD +54 -0
- {unrealon-1.1.1.dist-info → unrealon-1.1.5.dist-info}/entry_points.txt +1 -1
- unrealon_browser/__init__.py +3 -6
- unrealon_browser/core/browser_manager.py +86 -84
- unrealon_browser/dto/models/config.py +2 -0
- unrealon_browser/managers/captcha.py +165 -185
- unrealon_browser/managers/cookies.py +57 -28
- unrealon_browser/managers/logger_bridge.py +94 -34
- unrealon_browser/managers/profile.py +186 -158
- unrealon_browser/managers/stealth.py +58 -47
- unrealon_driver/__init__.py +8 -21
- unrealon_driver/exceptions.py +5 -0
- unrealon_driver/html_analyzer/__init__.py +32 -0
- unrealon_driver/{parser/managers/html.py → html_analyzer/cleaner.py} +330 -405
- unrealon_driver/html_analyzer/config.py +64 -0
- unrealon_driver/html_analyzer/manager.py +247 -0
- unrealon_driver/html_analyzer/models.py +115 -0
- unrealon_driver/html_analyzer/websocket_analyzer.py +157 -0
- unrealon_driver/models/__init__.py +31 -0
- unrealon_driver/models/websocket.py +98 -0
- unrealon_driver/parser/__init__.py +4 -23
- unrealon_driver/parser/cli_manager.py +6 -5
- unrealon_driver/parser/daemon_manager.py +242 -66
- unrealon_driver/parser/managers/__init__.py +0 -21
- unrealon_driver/parser/managers/config.py +15 -3
- unrealon_driver/parser/parser_manager.py +225 -395
- unrealon_driver/smart_logging/__init__.py +24 -0
- unrealon_driver/smart_logging/models.py +44 -0
- unrealon_driver/smart_logging/smart_logger.py +406 -0
- unrealon_driver/smart_logging/unified_logger.py +525 -0
- unrealon_driver/websocket/__init__.py +31 -0
- unrealon_driver/websocket/client.py +249 -0
- unrealon_driver/websocket/config.py +188 -0
- unrealon_driver/websocket/manager.py +90 -0
- unrealon-1.1.1.dist-info/METADATA +0 -722
- unrealon-1.1.1.dist-info/RECORD +0 -82
- unrealon_bridge/__init__.py +0 -114
- unrealon_bridge/cli.py +0 -316
- unrealon_bridge/client/__init__.py +0 -93
- unrealon_bridge/client/base.py +0 -78
- unrealon_bridge/client/commands.py +0 -89
- unrealon_bridge/client/connection.py +0 -90
- unrealon_bridge/client/events.py +0 -65
- unrealon_bridge/client/health.py +0 -38
- unrealon_bridge/client/html_parser.py +0 -146
- unrealon_bridge/client/logging.py +0 -139
- unrealon_bridge/client/proxy.py +0 -70
- unrealon_bridge/client/scheduler.py +0 -450
- unrealon_bridge/client/session.py +0 -70
- unrealon_bridge/configs/__init__.py +0 -14
- unrealon_bridge/configs/bridge_config.py +0 -212
- unrealon_bridge/configs/bridge_config.yaml +0 -39
- unrealon_bridge/models/__init__.py +0 -138
- unrealon_bridge/models/base.py +0 -28
- unrealon_bridge/models/command.py +0 -41
- unrealon_bridge/models/events.py +0 -40
- unrealon_bridge/models/html_parser.py +0 -79
- unrealon_bridge/models/logging.py +0 -55
- unrealon_bridge/models/parser.py +0 -63
- unrealon_bridge/models/proxy.py +0 -41
- unrealon_bridge/models/requests.py +0 -95
- unrealon_bridge/models/responses.py +0 -88
- unrealon_bridge/models/scheduler.py +0 -592
- unrealon_bridge/models/session.py +0 -28
- unrealon_bridge/server/__init__.py +0 -91
- unrealon_bridge/server/base.py +0 -171
- unrealon_bridge/server/handlers/__init__.py +0 -23
- unrealon_bridge/server/handlers/command.py +0 -110
- unrealon_bridge/server/handlers/html_parser.py +0 -139
- unrealon_bridge/server/handlers/logging.py +0 -95
- unrealon_bridge/server/handlers/parser.py +0 -95
- unrealon_bridge/server/handlers/proxy.py +0 -75
- unrealon_bridge/server/handlers/scheduler.py +0 -545
- unrealon_bridge/server/handlers/session.py +0 -66
- unrealon_driver/browser/__init__.py +0 -8
- unrealon_driver/browser/config.py +0 -74
- unrealon_driver/browser/manager.py +0 -416
- unrealon_driver/parser/managers/browser.py +0 -51
- unrealon_driver/parser/managers/logging.py +0 -609
- {unrealon-1.1.1.dist-info → unrealon-1.1.5.dist-info}/WHEEL +0 -0
- {unrealon-1.1.1.dist-info → unrealon-1.1.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,41 +9,65 @@ import sqlite3
|
|
|
9
9
|
from datetime import datetime, timezone
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Dict, Any, Optional, List, Union
|
|
12
|
-
|
|
12
|
+
import logging
|
|
13
13
|
from unrealon_browser.dto import ProfileType, ProxyInfo
|
|
14
14
|
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
class ProfileManager:
|
|
17
19
|
"""
|
|
18
20
|
Browser profile lifecycle management with proxy binding
|
|
19
|
-
|
|
21
|
+
|
|
20
22
|
Layer 2: Profile management capabilities
|
|
21
23
|
- Proxy-based profile generation
|
|
22
24
|
- Profile metadata tracking
|
|
23
25
|
- Automatic cleanup policies
|
|
24
26
|
- Profile health monitoring
|
|
25
27
|
"""
|
|
26
|
-
|
|
27
|
-
def __init__(self, profiles_dir: str = "./browser_profiles"):
|
|
28
|
+
|
|
29
|
+
def __init__(self, profiles_dir: str = "./browser_profiles", logger_bridge=None):
|
|
28
30
|
"""Initialize profile manager"""
|
|
29
31
|
self.profiles_dir = Path(profiles_dir)
|
|
30
32
|
self.profiles_dir.mkdir(parents=True, exist_ok=True)
|
|
31
|
-
|
|
33
|
+
self.logger_bridge = logger_bridge
|
|
34
|
+
|
|
32
35
|
# Initialize metadata database
|
|
33
36
|
self.db_path = self.profiles_dir / "profiles_metadata.db"
|
|
34
37
|
self._init_metadata_db()
|
|
35
|
-
|
|
38
|
+
|
|
36
39
|
self.current_profile_path: Optional[Path] = None
|
|
37
40
|
self.current_profile_type: Optional[ProfileType] = None
|
|
38
41
|
self.current_proxy_info: Optional[ProxyInfo] = None
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
|
|
43
|
+
self._logger(f"📁 Profile manager initialized: {self.profiles_dir}", "info")
|
|
44
|
+
|
|
45
|
+
def _logger(self, message: str, level: str = "info") -> None:
|
|
46
|
+
if self.logger_bridge:
|
|
47
|
+
if level == "info":
|
|
48
|
+
self.logger_bridge.log_info(message)
|
|
49
|
+
elif level == "error":
|
|
50
|
+
self.logger_bridge.log_error(message)
|
|
51
|
+
elif level == "warning":
|
|
52
|
+
self.logger_bridge.log_warning(message)
|
|
53
|
+
else:
|
|
54
|
+
self.logger_bridge.log_info(message)
|
|
55
|
+
else:
|
|
56
|
+
if level == "info":
|
|
57
|
+
logger.info(message)
|
|
58
|
+
elif level == "error":
|
|
59
|
+
logger.error(message)
|
|
60
|
+
elif level == "warning":
|
|
61
|
+
logger.warning(message)
|
|
62
|
+
else:
|
|
63
|
+
logger.info(message)
|
|
64
|
+
|
|
42
65
|
def _init_metadata_db(self) -> None:
|
|
43
66
|
"""Initialize SQLite metadata database"""
|
|
44
67
|
try:
|
|
45
68
|
with sqlite3.connect(self.db_path) as conn:
|
|
46
|
-
conn.execute(
|
|
69
|
+
conn.execute(
|
|
70
|
+
"""
|
|
47
71
|
CREATE TABLE IF NOT EXISTS profiles (
|
|
48
72
|
profile_name TEXT PRIMARY KEY,
|
|
49
73
|
profile_type TEXT NOT NULL,
|
|
@@ -60,12 +84,13 @@ class ProfileManager:
|
|
|
60
84
|
failure_count INTEGER DEFAULT 0,
|
|
61
85
|
metadata_json TEXT
|
|
62
86
|
)
|
|
63
|
-
"""
|
|
87
|
+
"""
|
|
88
|
+
)
|
|
64
89
|
conn.commit()
|
|
65
|
-
|
|
90
|
+
self._logger(" ✅ Profile metadata database initialized", "info")
|
|
66
91
|
except Exception as e:
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
self._logger(f"❌ Failed to initialize metadata database: {e}", "error")
|
|
93
|
+
|
|
69
94
|
def _generate_profile_name(self, parser_name: str, proxy_info: Optional[ProxyInfo] = None) -> str:
|
|
70
95
|
"""Generate profile name based on parser and proxy"""
|
|
71
96
|
if proxy_info:
|
|
@@ -75,13 +100,8 @@ class ProfileManager:
|
|
|
75
100
|
# Direct connection profile name
|
|
76
101
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
77
102
|
return f"{parser_name}_direct_{timestamp}"
|
|
78
|
-
|
|
79
|
-
def create_profile(
|
|
80
|
-
self,
|
|
81
|
-
parser_name: str,
|
|
82
|
-
proxy_info: Optional[ProxyInfo] = None,
|
|
83
|
-
profile_type: ProfileType = ProfileType.PROXY_BOUND
|
|
84
|
-
) -> Path:
|
|
103
|
+
|
|
104
|
+
def create_profile(self, parser_name: str, proxy_info: Optional[ProxyInfo] = None, profile_type: ProfileType = ProfileType.PROXY_BOUND) -> Path:
|
|
85
105
|
"""
|
|
86
106
|
Create browser profile directory
|
|
87
107
|
Inspired by unrealparser's profile creation strategy
|
|
@@ -90,192 +110,192 @@ class ProfileManager:
|
|
|
90
110
|
# Generate profile name
|
|
91
111
|
profile_name = self._generate_profile_name(parser_name, proxy_info)
|
|
92
112
|
profile_path = self.profiles_dir / profile_name
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
|
|
114
|
+
self._logger(f"📁 Creating profile: {profile_name}", "info")
|
|
115
|
+
self._logger(f" Type: {profile_type.value}", "info")
|
|
96
116
|
if proxy_info:
|
|
97
|
-
|
|
98
|
-
|
|
117
|
+
self._logger(f" Proxy: {proxy_info.host}:{proxy_info.port}", "info")
|
|
118
|
+
|
|
99
119
|
# Create profile directory
|
|
100
120
|
profile_path.mkdir(parents=True, exist_ok=True)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
121
|
+
|
|
122
|
+
self._logger(f" 📂 Profile directory created:", "info")
|
|
123
|
+
self._logger(f" Path: {profile_path}", "info")
|
|
124
|
+
self._logger(f" Absolute: {profile_path.resolve()}", "info")
|
|
125
|
+
self._logger(f" Exists: {profile_path.exists()}", "info")
|
|
126
|
+
self._logger(f" Is directory: {profile_path.is_dir()}", "info")
|
|
127
|
+
|
|
109
128
|
# Store current profile info
|
|
110
129
|
self.current_profile_path = profile_path
|
|
111
130
|
self.current_profile_type = profile_type
|
|
112
131
|
self.current_proxy_info = proxy_info
|
|
113
|
-
|
|
132
|
+
|
|
114
133
|
# Save profile metadata to database
|
|
115
|
-
self._save_profile_metadata(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
proxy_info=proxy_info,
|
|
119
|
-
profile_path=profile_path
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
print(f" ✅ Profile created: {profile_path}")
|
|
134
|
+
self._save_profile_metadata(profile_name=profile_name, profile_type=profile_type, proxy_info=proxy_info, profile_path=profile_path)
|
|
135
|
+
|
|
136
|
+
self._logger(f" ✅ Profile created: {profile_path}", "info")
|
|
123
137
|
return profile_path
|
|
124
|
-
|
|
138
|
+
|
|
125
139
|
except Exception as e:
|
|
126
|
-
|
|
140
|
+
self._logger(f"❌ Failed to create profile: {e}", "error")
|
|
127
141
|
raise
|
|
128
|
-
|
|
129
|
-
def _save_profile_metadata(
|
|
130
|
-
self,
|
|
131
|
-
profile_name: str,
|
|
132
|
-
profile_type: ProfileType,
|
|
133
|
-
proxy_info: Optional[ProxyInfo],
|
|
134
|
-
profile_path: Path
|
|
135
|
-
) -> None:
|
|
142
|
+
|
|
143
|
+
def _save_profile_metadata(self, profile_name: str, profile_type: ProfileType, proxy_info: Optional[ProxyInfo], profile_path: Path) -> None:
|
|
136
144
|
"""Save profile metadata to database"""
|
|
137
145
|
try:
|
|
138
146
|
# Calculate profile size
|
|
139
147
|
size_bytes = self._calculate_profile_size(profile_path)
|
|
140
|
-
|
|
148
|
+
|
|
141
149
|
# Prepare metadata
|
|
142
150
|
metadata = {
|
|
143
151
|
"profile_path": str(profile_path),
|
|
144
152
|
"parser_name": profile_name.split("_")[0],
|
|
145
153
|
"created_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
146
154
|
}
|
|
147
|
-
|
|
155
|
+
|
|
148
156
|
with sqlite3.connect(self.db_path) as conn:
|
|
149
|
-
conn.execute(
|
|
157
|
+
conn.execute(
|
|
158
|
+
"""
|
|
150
159
|
INSERT OR REPLACE INTO profiles (
|
|
151
160
|
profile_name, profile_type, proxy_host, proxy_port,
|
|
152
161
|
proxy_username, proxy_password, proxy_ip,
|
|
153
162
|
created_at, last_used_at, size_bytes, metadata_json
|
|
154
163
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
155
|
-
""",
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
""",
|
|
165
|
+
(
|
|
166
|
+
profile_name,
|
|
167
|
+
profile_type.value,
|
|
168
|
+
proxy_info.host if proxy_info else None,
|
|
169
|
+
proxy_info.port if proxy_info else None,
|
|
170
|
+
proxy_info.username if proxy_info else None,
|
|
171
|
+
proxy_info.password if proxy_info else None,
|
|
172
|
+
proxy_info.ip if proxy_info else None,
|
|
173
|
+
datetime.now(timezone.utc).isoformat(),
|
|
174
|
+
datetime.now(timezone.utc).isoformat(),
|
|
175
|
+
size_bytes,
|
|
176
|
+
json.dumps(metadata),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
168
179
|
conn.commit()
|
|
169
|
-
|
|
180
|
+
|
|
170
181
|
except Exception as e:
|
|
171
|
-
|
|
172
|
-
|
|
182
|
+
self._logger(f"❌ Failed to save profile metadata: {e}", "error")
|
|
183
|
+
|
|
173
184
|
def _calculate_profile_size(self, profile_path: Path) -> int:
|
|
174
185
|
"""Calculate total size of profile directory"""
|
|
175
186
|
try:
|
|
176
187
|
if not profile_path.exists():
|
|
177
188
|
return 0
|
|
178
|
-
|
|
189
|
+
|
|
179
190
|
total_size = 0
|
|
180
191
|
for file_path in profile_path.rglob("*"):
|
|
181
192
|
if file_path.is_file():
|
|
182
193
|
total_size += file_path.stat().st_size
|
|
183
194
|
return total_size
|
|
184
|
-
|
|
195
|
+
|
|
185
196
|
except Exception as e:
|
|
186
|
-
|
|
197
|
+
self._logger(f"⚠️ Failed to calculate profile size: {e}", "warning")
|
|
187
198
|
return 0
|
|
188
|
-
|
|
199
|
+
|
|
189
200
|
def get_profile_for_proxy(self, parser_name: str, proxy_info: ProxyInfo) -> Optional[Path]:
|
|
190
201
|
"""Get existing profile for specific proxy"""
|
|
191
202
|
try:
|
|
192
203
|
profile_name = self._generate_profile_name(parser_name, proxy_info)
|
|
193
204
|
profile_path = self.profiles_dir / profile_name
|
|
194
|
-
|
|
205
|
+
|
|
195
206
|
if profile_path.exists():
|
|
196
|
-
|
|
197
|
-
|
|
207
|
+
self._logger(f"📁 Found existing profile: {profile_name}", "info")
|
|
208
|
+
|
|
198
209
|
# Update last used timestamp
|
|
199
210
|
self._update_profile_usage(profile_name)
|
|
200
|
-
|
|
211
|
+
|
|
201
212
|
# Store current profile info
|
|
202
213
|
self.current_profile_path = profile_path
|
|
203
214
|
self.current_profile_type = ProfileType.PROXY_BOUND
|
|
204
215
|
self.current_proxy_info = proxy_info
|
|
205
|
-
|
|
216
|
+
|
|
206
217
|
return profile_path
|
|
207
|
-
|
|
218
|
+
|
|
208
219
|
return None
|
|
209
|
-
|
|
220
|
+
|
|
210
221
|
except Exception as e:
|
|
211
|
-
|
|
222
|
+
self._logger(f"❌ Failed to get profile for proxy: {e}", "error")
|
|
212
223
|
return None
|
|
213
|
-
|
|
224
|
+
|
|
214
225
|
def _update_profile_usage(self, profile_name: str) -> None:
|
|
215
226
|
"""Update profile usage statistics"""
|
|
216
227
|
try:
|
|
217
228
|
with sqlite3.connect(self.db_path) as conn:
|
|
218
|
-
conn.execute(
|
|
229
|
+
conn.execute(
|
|
230
|
+
"""
|
|
219
231
|
UPDATE profiles
|
|
220
232
|
SET last_used_at = ?, session_count = session_count + 1
|
|
221
233
|
WHERE profile_name = ?
|
|
222
|
-
""",
|
|
234
|
+
""",
|
|
235
|
+
(datetime.now(timezone.utc).isoformat(), profile_name),
|
|
236
|
+
)
|
|
223
237
|
conn.commit()
|
|
224
|
-
|
|
238
|
+
|
|
225
239
|
except Exception as e:
|
|
226
|
-
|
|
227
|
-
|
|
240
|
+
self._logger(f"⚠️ Failed to update profile usage: {e}", "warning")
|
|
241
|
+
|
|
228
242
|
def mark_session_success(self, success: bool = True) -> None:
|
|
229
243
|
"""Mark current session as success or failure"""
|
|
230
244
|
if not self.current_profile_path:
|
|
231
245
|
return
|
|
232
|
-
|
|
246
|
+
|
|
233
247
|
try:
|
|
234
248
|
profile_name = self.current_profile_path.name
|
|
235
|
-
|
|
249
|
+
|
|
236
250
|
with sqlite3.connect(self.db_path) as conn:
|
|
237
251
|
if success:
|
|
238
|
-
conn.execute(
|
|
252
|
+
conn.execute(
|
|
253
|
+
"""
|
|
239
254
|
UPDATE profiles
|
|
240
255
|
SET success_count = success_count + 1
|
|
241
256
|
WHERE profile_name = ?
|
|
242
|
-
""",
|
|
257
|
+
""",
|
|
258
|
+
(profile_name,),
|
|
259
|
+
)
|
|
243
260
|
else:
|
|
244
|
-
conn.execute(
|
|
261
|
+
conn.execute(
|
|
262
|
+
"""
|
|
245
263
|
UPDATE profiles
|
|
246
264
|
SET failure_count = failure_count + 1
|
|
247
265
|
WHERE profile_name = ?
|
|
248
|
-
""",
|
|
266
|
+
""",
|
|
267
|
+
(profile_name,),
|
|
268
|
+
)
|
|
249
269
|
conn.commit()
|
|
250
|
-
|
|
270
|
+
|
|
251
271
|
except Exception as e:
|
|
252
|
-
|
|
253
|
-
|
|
272
|
+
self._logger(f"⚠️ Failed to mark session result: {e}", "warning")
|
|
273
|
+
|
|
254
274
|
def list_profiles(self, parser_name: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
255
275
|
"""List all profiles with metadata"""
|
|
256
276
|
try:
|
|
257
277
|
query = "SELECT * FROM profiles"
|
|
258
278
|
params = []
|
|
259
|
-
|
|
279
|
+
|
|
260
280
|
if parser_name:
|
|
261
281
|
query += " WHERE profile_name LIKE ?"
|
|
262
282
|
params.append(f"{parser_name}_%")
|
|
263
|
-
|
|
283
|
+
|
|
264
284
|
query += " ORDER BY last_used_at DESC"
|
|
265
|
-
|
|
285
|
+
|
|
266
286
|
with sqlite3.connect(self.db_path) as conn:
|
|
267
287
|
conn.row_factory = sqlite3.Row
|
|
268
288
|
cursor = conn.execute(query, params)
|
|
269
289
|
profiles = []
|
|
270
|
-
|
|
290
|
+
|
|
271
291
|
for row in cursor.fetchall():
|
|
272
292
|
profile_data = dict(row)
|
|
273
|
-
|
|
293
|
+
|
|
274
294
|
# Add calculated fields
|
|
275
295
|
profile_path = Path(profile_data["profile_name"])
|
|
276
296
|
profile_data["exists"] = (self.profiles_dir / profile_path).exists()
|
|
277
297
|
profile_data["size_mb"] = profile_data["size_bytes"] / (1024 * 1024) if profile_data["size_bytes"] else 0
|
|
278
|
-
|
|
298
|
+
|
|
279
299
|
# Calculate success rate
|
|
280
300
|
total_sessions = profile_data["session_count"] or 0
|
|
281
301
|
if total_sessions > 0:
|
|
@@ -283,104 +303,110 @@ class ProfileManager:
|
|
|
283
303
|
profile_data["success_rate"] = success_rate
|
|
284
304
|
else:
|
|
285
305
|
profile_data["success_rate"] = 0.0
|
|
286
|
-
|
|
306
|
+
|
|
287
307
|
profiles.append(profile_data)
|
|
288
|
-
|
|
308
|
+
|
|
289
309
|
return profiles
|
|
290
|
-
|
|
310
|
+
|
|
291
311
|
except Exception as e:
|
|
292
|
-
|
|
312
|
+
self._logger(f"❌ Failed to list profiles: {e}", "error")
|
|
293
313
|
return []
|
|
294
|
-
|
|
314
|
+
|
|
295
315
|
def cleanup_old_profiles(self, max_age_days: int = 30, max_size_mb: int = 1000) -> int:
|
|
296
316
|
"""
|
|
297
317
|
Cleanup old or oversized profiles
|
|
298
318
|
Inspired by unrealparser's cleanup strategy
|
|
299
319
|
"""
|
|
300
320
|
try:
|
|
301
|
-
|
|
302
|
-
|
|
321
|
+
self._logger(f"🧹 Cleaning up profiles older than {max_age_days} days or larger than {max_size_mb}MB", "info")
|
|
322
|
+
|
|
303
323
|
cutoff_date = datetime.now(timezone.utc).timestamp() - (max_age_days * 24 * 3600)
|
|
304
324
|
cleaned_count = 0
|
|
305
|
-
|
|
325
|
+
|
|
306
326
|
profiles = self.list_profiles()
|
|
307
|
-
|
|
327
|
+
|
|
308
328
|
for profile in profiles:
|
|
309
329
|
should_delete = False
|
|
310
330
|
reason = ""
|
|
311
|
-
|
|
331
|
+
|
|
312
332
|
# Check age
|
|
313
333
|
if profile["created_at"]:
|
|
314
|
-
created_timestamp = datetime.fromisoformat(profile["created_at"].replace(
|
|
334
|
+
created_timestamp = datetime.fromisoformat(profile["created_at"].replace("Z", "+00:00")).timestamp()
|
|
315
335
|
if created_timestamp < cutoff_date:
|
|
316
336
|
should_delete = True
|
|
317
337
|
reason = f"older than {max_age_days} days"
|
|
318
|
-
|
|
338
|
+
|
|
319
339
|
# Check size
|
|
320
340
|
if profile["size_mb"] > max_size_mb:
|
|
321
341
|
should_delete = True
|
|
322
342
|
reason = f"larger than {max_size_mb}MB ({profile['size_mb']:.1f}MB)"
|
|
323
|
-
|
|
343
|
+
|
|
324
344
|
if should_delete and profile["exists"]:
|
|
325
345
|
profile_path = self.profiles_dir / profile["profile_name"]
|
|
326
346
|
try:
|
|
327
347
|
shutil.rmtree(profile_path)
|
|
328
|
-
|
|
348
|
+
self._logger(f" 🗑️ Deleted profile {profile['profile_name']} ({reason})", "info")
|
|
329
349
|
cleaned_count += 1
|
|
330
|
-
|
|
350
|
+
|
|
331
351
|
# Remove from database
|
|
332
352
|
with sqlite3.connect(self.db_path) as conn:
|
|
333
353
|
conn.execute("DELETE FROM profiles WHERE profile_name = ?", (profile["profile_name"],))
|
|
334
354
|
conn.commit()
|
|
335
|
-
|
|
355
|
+
|
|
336
356
|
except Exception as e:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
357
|
+
self._logger(f" ❌ Failed to delete profile {profile['profile_name']}: {e}", "error")
|
|
358
|
+
|
|
359
|
+
self._logger(f"✅ Cleanup completed: {cleaned_count} profiles removed", "info")
|
|
340
360
|
return cleaned_count
|
|
341
|
-
|
|
361
|
+
|
|
342
362
|
except Exception as e:
|
|
343
|
-
|
|
363
|
+
self._logger(f"❌ Profile cleanup failed: {e}", "error")
|
|
344
364
|
return 0
|
|
345
|
-
|
|
365
|
+
|
|
346
366
|
def get_profile_statistics(self) -> Dict[str, Any]:
|
|
347
367
|
"""Get overall profile statistics"""
|
|
348
368
|
try:
|
|
349
369
|
with sqlite3.connect(self.db_path) as conn:
|
|
350
370
|
conn.row_factory = sqlite3.Row
|
|
351
|
-
|
|
371
|
+
|
|
352
372
|
# Basic counts
|
|
353
373
|
cursor = conn.execute("SELECT COUNT(*) as total_profiles FROM profiles")
|
|
354
374
|
total_profiles = cursor.fetchone()["total_profiles"]
|
|
355
|
-
|
|
375
|
+
|
|
356
376
|
# Profile types
|
|
357
|
-
cursor = conn.execute(
|
|
377
|
+
cursor = conn.execute(
|
|
378
|
+
"""
|
|
358
379
|
SELECT profile_type, COUNT(*) as count
|
|
359
380
|
FROM profiles
|
|
360
381
|
GROUP BY profile_type
|
|
361
|
-
"""
|
|
382
|
+
"""
|
|
383
|
+
)
|
|
362
384
|
profile_types = {row["profile_type"]: row["count"] for row in cursor.fetchall()}
|
|
363
|
-
|
|
385
|
+
|
|
364
386
|
# Size statistics
|
|
365
|
-
cursor = conn.execute(
|
|
387
|
+
cursor = conn.execute(
|
|
388
|
+
"""
|
|
366
389
|
SELECT
|
|
367
390
|
SUM(size_bytes) as total_size,
|
|
368
391
|
AVG(size_bytes) as avg_size,
|
|
369
392
|
MAX(size_bytes) as max_size
|
|
370
393
|
FROM profiles
|
|
371
|
-
"""
|
|
394
|
+
"""
|
|
395
|
+
)
|
|
372
396
|
size_stats = cursor.fetchone()
|
|
373
|
-
|
|
397
|
+
|
|
374
398
|
# Session statistics
|
|
375
|
-
cursor = conn.execute(
|
|
399
|
+
cursor = conn.execute(
|
|
400
|
+
"""
|
|
376
401
|
SELECT
|
|
377
402
|
SUM(session_count) as total_sessions,
|
|
378
403
|
SUM(success_count) as total_successes,
|
|
379
404
|
SUM(failure_count) as total_failures
|
|
380
405
|
FROM profiles
|
|
381
|
-
"""
|
|
406
|
+
"""
|
|
407
|
+
)
|
|
382
408
|
session_stats = cursor.fetchone()
|
|
383
|
-
|
|
409
|
+
|
|
384
410
|
return {
|
|
385
411
|
"total_profiles": total_profiles,
|
|
386
412
|
"profile_types": profile_types,
|
|
@@ -390,47 +416,49 @@ class ProfileManager:
|
|
|
390
416
|
"total_sessions": session_stats["total_sessions"] or 0,
|
|
391
417
|
"total_successes": session_stats["total_successes"] or 0,
|
|
392
418
|
"total_failures": session_stats["total_failures"] or 0,
|
|
393
|
-
"overall_success_rate": (
|
|
394
|
-
(session_stats["total_successes"] or 0) / (session_stats["total_sessions"] or 1)
|
|
395
|
-
if session_stats["total_sessions"] else 0.0
|
|
396
|
-
),
|
|
419
|
+
"overall_success_rate": ((session_stats["total_successes"] or 0) / (session_stats["total_sessions"] or 1) if session_stats["total_sessions"] else 0.0),
|
|
397
420
|
}
|
|
398
|
-
|
|
421
|
+
|
|
399
422
|
except Exception as e:
|
|
400
|
-
|
|
423
|
+
self._logger(f"❌ Failed to get profile statistics: {e}", "error")
|
|
401
424
|
return {}
|
|
402
|
-
|
|
425
|
+
|
|
403
426
|
def print_profile_statistics(self) -> None:
|
|
404
427
|
"""Print profile statistics"""
|
|
405
428
|
stats = self.get_profile_statistics()
|
|
406
|
-
|
|
429
|
+
|
|
407
430
|
print("\n📁 Profile Manager Statistics:")
|
|
408
431
|
print(f" Total profiles: {stats.get('total_profiles', 0)}")
|
|
409
432
|
print(f" Total size: {stats.get('total_size_mb', 0):.1f}MB")
|
|
410
433
|
print(f" Average size: {stats.get('avg_size_mb', 0):.1f}MB")
|
|
411
434
|
print(f" Total sessions: {stats.get('total_sessions', 0)}")
|
|
412
435
|
print(f" Success rate: {stats.get('overall_success_rate', 0):.1%}")
|
|
413
|
-
|
|
414
|
-
profile_types = stats.get(
|
|
436
|
+
|
|
437
|
+
profile_types = stats.get("profile_types", {})
|
|
415
438
|
if profile_types:
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
439
|
+
if self.logger_bridge:
|
|
440
|
+
self.logger_bridge.log_info(" Profile types:")
|
|
441
|
+
for ptype, count in profile_types.items():
|
|
442
|
+
self.logger_bridge.log_info(f" {ptype}: {count}")
|
|
443
|
+
|
|
420
444
|
def get_current_profile_info(self) -> Optional[Dict[str, Any]]:
|
|
421
445
|
"""Get information about current active profile"""
|
|
422
446
|
if not self.current_profile_path:
|
|
423
447
|
return None
|
|
424
|
-
|
|
448
|
+
|
|
425
449
|
return {
|
|
426
450
|
"profile_path": str(self.current_profile_path),
|
|
427
451
|
"profile_name": self.current_profile_path.name,
|
|
428
452
|
"profile_type": self.current_profile_type.value if self.current_profile_type else None,
|
|
429
|
-
"proxy_info":
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
453
|
+
"proxy_info": (
|
|
454
|
+
{
|
|
455
|
+
"host": self.current_proxy_info.host,
|
|
456
|
+
"port": self.current_proxy_info.port,
|
|
457
|
+
"ip": self.current_proxy_info.ip,
|
|
458
|
+
}
|
|
459
|
+
if self.current_proxy_info
|
|
460
|
+
else None
|
|
461
|
+
),
|
|
434
462
|
"exists": self.current_profile_path.exists(),
|
|
435
463
|
"size_mb": self._calculate_profile_size(self.current_profile_path) / (1024 * 1024),
|
|
436
464
|
}
|