frida-fusion 0.1.16__tar.gz → 0.1.20__tar.gz
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 frida-fusion might be problematic. Click here for more details.
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/PKG-INFO +3 -1
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/README.md +2 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/__meta__.py +2 -2
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/config.py +16 -2
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/fusion.py +118 -61
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/libs/database.py +45 -21
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/libs/helpers.js +154 -8
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/libs/logger.py +40 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/module.py +73 -5
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/crypto/crypto.py +9 -3
- frida_fusion-0.1.20/frida_fusion/modules/hermes_injector/hermes_hook.js +36 -0
- frida_fusion-0.1.20/frida_fusion/modules/hermes_injector/hermes_injector.js +191 -0
- frida_fusion-0.1.20/frida_fusion/modules/hermes_injector/hermes_injector.py +89 -0
- frida_fusion-0.1.20/frida_fusion/modules/okhttp_logging/okhttp-logging.js +1584 -0
- frida_fusion-0.1.20/frida_fusion/modules/okhttp_logging/okhttp-logging.py +134 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/reflection/reflection-stalker.py +1 -1
- frida_fusion-0.1.20/frida_fusion/modules/shared_preferences/__init__.py +0 -0
- frida_fusion-0.1.20/frida_fusion/modules/shared_preferences/shared_preferences.js +448 -0
- frida_fusion-0.1.20/frida_fusion/modules/shared_preferences/shared_preferences.py +179 -0
- frida_fusion-0.1.20/frida_fusion/modules/tls_unpinning/__init__.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion.egg-info/PKG-INFO +3 -1
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion.egg-info/SOURCES.txt +9 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/LICENSE +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/__init__.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/__main__.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/args.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/exceptions.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/libs/__init__.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/libs/color.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/libs/scriptlocation.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/__init__.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/android_setings/__init__.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/android_setings/settings.js +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/android_setings/settings.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/crypto/__init__.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/crypto/crypto.js +0 -0
- {frida_fusion-0.1.16/frida_fusion/modules/tls_unpinning → frida_fusion-0.1.20/frida_fusion/modules/hermes_injector}/__init__.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/reflection/reflection-stalker.js +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion/modules/tls_unpinning/frida_multiple_unpinning.py +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion.egg-info/dependency_links.txt +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion.egg-info/entry_points.txt +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion.egg-info/requires.txt +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/frida_fusion.egg-info/top_level.txt +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/pyproject.toml +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/setup.cfg +0 -0
- {frida_fusion-0.1.16 → frida_fusion-0.1.20}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frida-fusion
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.20
|
|
4
4
|
Summary: Hook your mobile tests with Frida
|
|
5
5
|
Author-email: "Helvio Junior (M4v3r1ck)" <helvio_junior@hotmail.com>
|
|
6
6
|
Maintainer-email: "Helvio Junior (M4v3r1ck)" <helvio_junior@hotmail.com>
|
|
@@ -34,6 +34,7 @@ Requires-Dist: frida-tools>=10.8.0
|
|
|
34
34
|
Dynamic: license-file
|
|
35
35
|
|
|
36
36
|
# Frida Fusion
|
|
37
|
+
<img src="./fusion_logo.svg" alt="Frida Fusion logo" align="right" width="20%"/>
|
|
37
38
|
|
|
38
39
|
Hook your mobile tests with Frida.
|
|
39
40
|
|
|
@@ -92,6 +93,7 @@ The Frida Fusion define/expose several functions to be used at frida scripts. Fo
|
|
|
92
93
|
```java
|
|
93
94
|
# Send message/data to Frida-Fusion
|
|
94
95
|
void fusion_sendMessage(String level, String message);
|
|
96
|
+
void fusion_sendError(Error error);
|
|
95
97
|
void fusion_sendMessageWithTrace(String level, String message);
|
|
96
98
|
void fusion_sendKeyValueData(String module, Object items);
|
|
97
99
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Frida Fusion
|
|
2
|
+
<img src="./fusion_logo.svg" alt="Frida Fusion logo" align="right" width="20%"/>
|
|
2
3
|
|
|
3
4
|
Hook your mobile tests with Frida.
|
|
4
5
|
|
|
@@ -57,6 +58,7 @@ The Frida Fusion define/expose several functions to be used at frida scripts. Fo
|
|
|
57
58
|
```java
|
|
58
59
|
# Send message/data to Frida-Fusion
|
|
59
60
|
void fusion_sendMessage(String level, String message);
|
|
61
|
+
void fusion_sendError(Error error);
|
|
60
62
|
void fusion_sendMessageWithTrace(String level, String message);
|
|
61
63
|
void fusion_sendKeyValueData(String module, Object items);
|
|
62
64
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
__version__ = '0.1.
|
|
1
|
+
__version__ = '0.1.20'
|
|
2
2
|
__title__ = "Frida Fusion"
|
|
3
3
|
__description__ = "📱 frida-fusion - runtime mobile exploration"
|
|
4
4
|
__url__ = "https://github.com/helviojunior/frida-fusion"
|
|
5
|
-
__build__ =
|
|
5
|
+
__build__ = 0x472df92
|
|
6
6
|
__author__ = "Helvio Junior (M4v3r1ck)"
|
|
7
7
|
__author_email__ = "helvio_junior@hotmail.com"
|
|
8
8
|
__license__ = "GPL-3.0"
|
|
@@ -7,7 +7,7 @@ import signal
|
|
|
7
7
|
from argparse import Namespace
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
from .module import Module, ModuleManager, InternalModule, ExternalModule
|
|
10
|
+
from .module import Module, ModuleManager, InternalModule, ExternalModule, LocalModule
|
|
11
11
|
from .libs.color import Color
|
|
12
12
|
from .libs.logger import Logger
|
|
13
13
|
from .__meta__ import __version__
|
|
@@ -70,6 +70,8 @@ class Configuration(object):
|
|
|
70
70
|
|
|
71
71
|
sys.argv[0] = 'frida-fusion'
|
|
72
72
|
|
|
73
|
+
Configuration.cmd_line = ' '.join([word for word in sys.argv])
|
|
74
|
+
|
|
73
75
|
list_modules = any(['--list-modules' == word for word in sys.argv])
|
|
74
76
|
#show_help = any(['-h' == word for word in sys.argv])
|
|
75
77
|
|
|
@@ -132,6 +134,8 @@ class Configuration(object):
|
|
|
132
134
|
Color.pl('{!} {R}error: you must specify just one parameter {O}--package{R} or {O}--attach-pid{R}{W}\r\n')
|
|
133
135
|
Configuration.mandatory()
|
|
134
136
|
|
|
137
|
+
Logger.pl(' {C}command line:{O} %s{W}' % Configuration.cmd_line)
|
|
138
|
+
|
|
135
139
|
if args.app_id is not None:
|
|
136
140
|
Configuration.package = args.app_id
|
|
137
141
|
Logger.pl(' {C}package:{O} %s{W}' % Configuration.package)
|
|
@@ -184,9 +188,10 @@ class Configuration(object):
|
|
|
184
188
|
|
|
185
189
|
Logger.pl(' {C}min debug level:{O} %s{W}' % str(args.debug_level).upper())
|
|
186
190
|
|
|
191
|
+
mods = ModuleManager.list_modules(local_path=Path(Configuration.frida_scripts))
|
|
187
192
|
if (args.enabled_modules is not None and isinstance(args.enabled_modules, list)) or \
|
|
188
193
|
(args.ignore_messages_modules is not None and isinstance(args.ignore_messages_modules, list)):
|
|
189
|
-
|
|
194
|
+
|
|
190
195
|
for mod in [
|
|
191
196
|
m.strip()
|
|
192
197
|
for md in args.enabled_modules
|
|
@@ -206,6 +211,7 @@ class Configuration(object):
|
|
|
206
211
|
name = fm.safe_name()
|
|
207
212
|
if name not in Configuration.enabled_modules.keys():
|
|
208
213
|
Configuration.enabled_modules[name] = fm
|
|
214
|
+
|
|
209
215
|
if args.ignore_messages_modules is not None and isinstance(args.ignore_messages_modules, list):
|
|
210
216
|
for mod in [
|
|
211
217
|
m.strip()
|
|
@@ -227,6 +233,14 @@ class Configuration(object):
|
|
|
227
233
|
if name not in Configuration.ignore_messages_modules.keys():
|
|
228
234
|
Configuration.ignore_messages_modules[name] = fm
|
|
229
235
|
|
|
236
|
+
# Enable user defined local modules
|
|
237
|
+
for _, fm in mods.items():
|
|
238
|
+
if isinstance(fm, LocalModule):
|
|
239
|
+
name = fm.safe_name()
|
|
240
|
+
if name not in Configuration.ignore_messages_modules.keys():
|
|
241
|
+
Configuration.ignore_messages_modules[name] = fm
|
|
242
|
+
Configuration.enabled_modules[name] = fm
|
|
243
|
+
|
|
230
244
|
if len(Configuration.enabled_modules) > 0:
|
|
231
245
|
Logger.pl(' {C}modules:{O} %s{W}' % ', '.join([
|
|
232
246
|
m.name
|
|
@@ -35,6 +35,7 @@ class Fusion(object):
|
|
|
35
35
|
print_timestamp = False
|
|
36
36
|
max_filename = 28
|
|
37
37
|
|
|
38
|
+
_bundle_pattern = re.compile(r'(fusion_bundle\.js):(\d+)')
|
|
38
39
|
_script_name = Path(__file__).name
|
|
39
40
|
_db_jobs = queue.Queue()
|
|
40
41
|
|
|
@@ -83,9 +84,9 @@ class Fusion(object):
|
|
|
83
84
|
elif Configuration.remote_host is not None:
|
|
84
85
|
self.device = process.add_remote_device(Configuration.remote_host)
|
|
85
86
|
|
|
86
|
-
except Exception as
|
|
87
|
+
except Exception as err:
|
|
87
88
|
self.device = None
|
|
88
|
-
Logger.
|
|
89
|
+
Logger.print_exception(err)
|
|
89
90
|
|
|
90
91
|
return self.device
|
|
91
92
|
|
|
@@ -108,25 +109,6 @@ class Fusion(object):
|
|
|
108
109
|
if v[0] <= loc.get_int_line() <= v[1]
|
|
109
110
|
]), loc)
|
|
110
111
|
|
|
111
|
-
@classmethod
|
|
112
|
-
def print_message(cls, level: str = "*", message: str = "",
|
|
113
|
-
script_location: ScriptLocation = None):
|
|
114
|
-
|
|
115
|
-
if Fusion.running is False and Logger.debug_level >= 2:
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
if script_location is None:
|
|
119
|
-
script_location = ScriptLocation(
|
|
120
|
-
file_name=Fusion._script_name
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
Logger.print_message(
|
|
124
|
-
level=level,
|
|
125
|
-
message=message,
|
|
126
|
-
script_location=script_location,
|
|
127
|
-
filename_col_len=Fusion.max_filename
|
|
128
|
-
)
|
|
129
|
-
|
|
130
112
|
def load_all_scripts(self):
|
|
131
113
|
self.script_trace = {}
|
|
132
114
|
offset = 1
|
|
@@ -162,7 +144,8 @@ class Fusion(object):
|
|
|
162
144
|
src += dyn
|
|
163
145
|
|
|
164
146
|
if os.path.isfile(Configuration.frida_scripts):
|
|
165
|
-
|
|
147
|
+
if Path(Configuration.frida_scripts).suffix.lower() == ".js":
|
|
148
|
+
files_js += [Configuration.frida_scripts]
|
|
166
149
|
else:
|
|
167
150
|
files_js += [
|
|
168
151
|
os.path.join(Configuration.frida_scripts, f)
|
|
@@ -170,7 +153,15 @@ class Fusion(object):
|
|
|
170
153
|
if f.endswith(".js")
|
|
171
154
|
]
|
|
172
155
|
|
|
156
|
+
# Keep unique files
|
|
157
|
+
# Do not use list(set(files_js)) because it will lose the order of modules
|
|
158
|
+
done: set[str] = set()
|
|
173
159
|
for file_path in files_js:
|
|
160
|
+
if file_path in done:
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
done.add(file_path)
|
|
164
|
+
|
|
174
165
|
file_name = Path(file_path).name
|
|
175
166
|
file_data = self.sanitize_js(open(file_path, 'r', encoding='utf-8').read())
|
|
176
167
|
if '#NOLOAD' in file_data:
|
|
@@ -190,7 +181,7 @@ class Fusion(object):
|
|
|
190
181
|
|
|
191
182
|
line_cnt = len(file_data.split("\n")) - 1
|
|
192
183
|
|
|
193
|
-
self.script_trace[file_name] = (offset, offset + line_cnt)
|
|
184
|
+
self.script_trace[file_name] = (offset, offset + line_cnt - 1)
|
|
194
185
|
offset += line_cnt
|
|
195
186
|
|
|
196
187
|
src += file_data
|
|
@@ -204,10 +195,17 @@ class Fusion(object):
|
|
|
204
195
|
s = self.session.create_script(src, name="fusion_bundle")
|
|
205
196
|
s.on("message", self.make_handler("fusion_bundle.js")) # register the message handler
|
|
206
197
|
s.load()
|
|
207
|
-
except Exception as
|
|
208
|
-
|
|
198
|
+
except Exception as err:
|
|
209
199
|
try:
|
|
210
|
-
|
|
200
|
+
from traceback import format_exc
|
|
201
|
+
err_txt = 'Error:{O} %s{W}' % str(err)
|
|
202
|
+
err_txt += '\n{O}Full stack trace below\n'
|
|
203
|
+
err_txt += format_exc().strip()
|
|
204
|
+
|
|
205
|
+
err_txt = err_txt.replace('\n', '\n{W} ')
|
|
206
|
+
err_txt = err_txt.replace(' File', '{W}{D}File')
|
|
207
|
+
err_txt = err_txt.replace(' Exception: ', '{R}Exception: {O}')
|
|
208
|
+
|
|
211
209
|
pattern = re.compile(r'script\(line (\d+)\):')
|
|
212
210
|
matches = [
|
|
213
211
|
(
|
|
@@ -217,15 +215,16 @@ class Fusion(object):
|
|
|
217
215
|
line=m.group(1),
|
|
218
216
|
))
|
|
219
217
|
)
|
|
220
|
-
for m in pattern.finditer(
|
|
218
|
+
for m in pattern.finditer(err_txt)
|
|
221
219
|
]
|
|
222
220
|
for m in matches:
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
err_txt = err_txt.replace(m[0], f"{m[1].file_name}(line {m[1].line})")
|
|
222
|
+
|
|
223
|
+
Logger.pl(err_txt)
|
|
225
224
|
print("")
|
|
226
225
|
sys.exit(1)
|
|
227
|
-
except Exception:
|
|
228
|
-
Logger.
|
|
226
|
+
except Exception as e2:
|
|
227
|
+
Logger.print_exception(e2)
|
|
229
228
|
print("")
|
|
230
229
|
sys.exit(1)
|
|
231
230
|
|
|
@@ -334,10 +333,11 @@ class Fusion(object):
|
|
|
334
333
|
msg = base64.b64decode(msg).decode("UTF-8")
|
|
335
334
|
except:
|
|
336
335
|
pass
|
|
337
|
-
|
|
336
|
+
|
|
337
|
+
self.print_message_inst(mLevel, msg, script_location=script_location)
|
|
338
338
|
|
|
339
339
|
elif mType == "key_value_data":
|
|
340
|
-
self.
|
|
340
|
+
self.print_message_inst("V", "RAW JSON:\n %s" % (
|
|
341
341
|
json.dumps(jData, indent=4).replace("\n", "\n ")
|
|
342
342
|
), script_location=script_location)
|
|
343
343
|
|
|
@@ -367,7 +367,7 @@ class Fusion(object):
|
|
|
367
367
|
|
|
368
368
|
# Legacy
|
|
369
369
|
elif mType == "data":
|
|
370
|
-
self.
|
|
370
|
+
self.print_message_inst("V", "RAW JSON:\n %s" % (
|
|
371
371
|
json.dumps(jData, indent=4).replace("\n", "\n ")
|
|
372
372
|
), script_location=script_location)
|
|
373
373
|
|
|
@@ -391,16 +391,16 @@ class Fusion(object):
|
|
|
391
391
|
|
|
392
392
|
elif mType == "java-uncaught":
|
|
393
393
|
self.insert_history('frida', json.dumps(jData))
|
|
394
|
-
self.
|
|
394
|
+
self.print_message_inst("E", jData.get('stack', ''), script_location=script_location)
|
|
395
395
|
|
|
396
396
|
else:
|
|
397
|
-
self.
|
|
397
|
+
self.print_message_inst(mLevel, message, script_location=script_location)
|
|
398
398
|
|
|
399
399
|
except SilentKillError as sk:
|
|
400
400
|
skm = str(sk)
|
|
401
401
|
|
|
402
|
-
self.
|
|
403
|
-
|
|
402
|
+
self.print_message_inst("D", "Silent kill requested",
|
|
403
|
+
script_location=Logger.get_caller_info(stack_index=1))
|
|
404
404
|
Fusion.running = False
|
|
405
405
|
time.sleep(0.2)
|
|
406
406
|
if skm != "":
|
|
@@ -410,8 +410,8 @@ class Fusion(object):
|
|
|
410
410
|
|
|
411
411
|
except Exception as err:
|
|
412
412
|
script_location = ScriptLocation(file_name=Fusion._script_name)
|
|
413
|
-
self.
|
|
414
|
-
self.
|
|
413
|
+
self.print_message_inst("E", message, script_location=script_location)
|
|
414
|
+
self.print_message_inst("E", payload, script_location=script_location)
|
|
415
415
|
self.print_exception(err)
|
|
416
416
|
|
|
417
417
|
else:
|
|
@@ -425,7 +425,6 @@ class Fusion(object):
|
|
|
425
425
|
stack = "Stack trace:\n"
|
|
426
426
|
stack += message.get('stack', '')
|
|
427
427
|
|
|
428
|
-
pattern = re.compile(r'(fusion_bundle\.js):(\d+)')
|
|
429
428
|
matches = [
|
|
430
429
|
(
|
|
431
430
|
m.group(0),
|
|
@@ -434,7 +433,7 @@ class Fusion(object):
|
|
|
434
433
|
line=m.group(2),
|
|
435
434
|
))
|
|
436
435
|
)
|
|
437
|
-
for m in
|
|
436
|
+
for m in Fusion._bundle_pattern.finditer(stack)
|
|
438
437
|
]
|
|
439
438
|
for m in matches:
|
|
440
439
|
stack = stack.replace(m[0], f"{m[1].file_name}:{m[1].line}")
|
|
@@ -453,18 +452,19 @@ class Fusion(object):
|
|
|
453
452
|
"stack": stack
|
|
454
453
|
}))
|
|
455
454
|
|
|
456
|
-
self.
|
|
457
|
-
|
|
455
|
+
self.print_message_inst("F", description + stack,
|
|
456
|
+
script_location=script_location)
|
|
458
457
|
Fusion.running = False
|
|
459
458
|
time.sleep(0.2)
|
|
460
459
|
Logger.pl('\n{+} {O}Exiting...{O}{W}')
|
|
461
460
|
self.done.set()
|
|
462
461
|
else:
|
|
463
|
-
self.
|
|
464
|
-
self.
|
|
465
|
-
except:
|
|
466
|
-
self.
|
|
467
|
-
self.
|
|
462
|
+
self.print_message_inst("I", message, script_location=script_location)
|
|
463
|
+
self.print_message_inst("I", payload, script_location=script_location)
|
|
464
|
+
except Exception as e:
|
|
465
|
+
self.print_message_inst("I", message, script_location=script_location)
|
|
466
|
+
self.print_message_inst("I", payload, script_location=script_location)
|
|
467
|
+
self.print_exception(e)
|
|
468
468
|
|
|
469
469
|
return handler
|
|
470
470
|
|
|
@@ -482,6 +482,26 @@ class Fusion(object):
|
|
|
482
482
|
Logger.pl("")
|
|
483
483
|
self.done.set()
|
|
484
484
|
|
|
485
|
+
def _replace_location(self, message: str) -> str:
|
|
486
|
+
try:
|
|
487
|
+
matches = [
|
|
488
|
+
(
|
|
489
|
+
m.group(0),
|
|
490
|
+
self.translate_location(dict(
|
|
491
|
+
file_name=m.group(1),
|
|
492
|
+
line=m.group(2),
|
|
493
|
+
))
|
|
494
|
+
)
|
|
495
|
+
for m in Fusion._bundle_pattern.finditer(message)
|
|
496
|
+
]
|
|
497
|
+
for m in matches:
|
|
498
|
+
message = message.replace(m[0], f"{m[1].file_name}:{m[1].line}")
|
|
499
|
+
except Exception as e:
|
|
500
|
+
print(e)
|
|
501
|
+
pass
|
|
502
|
+
|
|
503
|
+
return message
|
|
504
|
+
|
|
485
505
|
def _raise_key_value_event(self,
|
|
486
506
|
script_location: ScriptLocation = None,
|
|
487
507
|
stack_trace: str = None,
|
|
@@ -497,11 +517,11 @@ class Fusion(object):
|
|
|
497
517
|
)
|
|
498
518
|
except SilentKillError as ske:
|
|
499
519
|
raise ske
|
|
500
|
-
except Exception as
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
520
|
+
except Exception as err:
|
|
521
|
+
self.print_exception(
|
|
522
|
+
err,
|
|
523
|
+
script_location=Logger.get_error_info_from_format_exc(stack_index=-1)
|
|
524
|
+
)
|
|
505
525
|
|
|
506
526
|
def _raise_data_event(self,
|
|
507
527
|
script_location: ScriptLocation = None,
|
|
@@ -516,11 +536,48 @@ class Fusion(object):
|
|
|
516
536
|
)
|
|
517
537
|
except SilentKillError as ske:
|
|
518
538
|
raise ske
|
|
519
|
-
except Exception as
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
539
|
+
except Exception as err:
|
|
540
|
+
self.print_exception(
|
|
541
|
+
err,
|
|
542
|
+
script_location=Logger.get_error_info_from_format_exc(stack_index=-1)
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
def print_message_inst(self, level: str = "*", message: str = "",
|
|
546
|
+
script_location: ScriptLocation = None):
|
|
547
|
+
|
|
548
|
+
return type(self)._print_message(
|
|
549
|
+
level=level,
|
|
550
|
+
message=self._replace_location(message),
|
|
551
|
+
script_location=script_location
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
@classmethod
|
|
555
|
+
def print_message(cls, level: str = "*", message: str = "",
|
|
556
|
+
script_location: ScriptLocation = None):
|
|
557
|
+
return cls._print_message(
|
|
558
|
+
level=level,
|
|
559
|
+
message=message,
|
|
560
|
+
script_location=script_location
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
@classmethod
|
|
564
|
+
def _print_message(cls, level: str = "*", message: str = "",
|
|
565
|
+
script_location: ScriptLocation = None):
|
|
566
|
+
|
|
567
|
+
if Fusion.running is False and Logger.debug_level >= 2:
|
|
568
|
+
return
|
|
569
|
+
|
|
570
|
+
if script_location is None:
|
|
571
|
+
script_location = ScriptLocation(
|
|
572
|
+
file_name=Fusion._script_name
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
Logger.print_message(
|
|
576
|
+
level=level,
|
|
577
|
+
message=message,
|
|
578
|
+
script_location=script_location,
|
|
579
|
+
filename_col_len=Fusion.max_filename
|
|
580
|
+
)
|
|
524
581
|
|
|
525
582
|
@classmethod
|
|
526
583
|
def insert_history(cls, source: str, data: str, stack_trace: str = ''):
|
|
@@ -539,7 +596,7 @@ class Fusion(object):
|
|
|
539
596
|
db.insert_history(**kwargs)
|
|
540
597
|
|
|
541
598
|
@classmethod
|
|
542
|
-
def print_exception(cls, err):
|
|
599
|
+
def print_exception(cls, err, script_location: ScriptLocation = None):
|
|
543
600
|
from traceback import format_exc
|
|
544
601
|
err_txt = 'Error:{O} %s{W}' % str(err)
|
|
545
602
|
err_txt += '\n{O}Full stack trace below\n'
|
|
@@ -553,7 +610,7 @@ class Fusion(object):
|
|
|
553
610
|
level="E",
|
|
554
611
|
message=Color.s(err_txt),
|
|
555
612
|
filename_col_len=Fusion.max_filename,
|
|
556
|
-
script_location=Logger.get_caller_info(stack_index=2)
|
|
613
|
+
script_location=script_location if script_location is not None else Logger.get_caller_info(stack_index=2)
|
|
557
614
|
)
|
|
558
615
|
|
|
559
616
|
@classmethod
|
|
@@ -4,6 +4,7 @@ import math
|
|
|
4
4
|
import shutil
|
|
5
5
|
import os.path
|
|
6
6
|
import sqlite3
|
|
7
|
+
import time
|
|
7
8
|
from functools import reduce
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from sqlite3 import Connection, ProgrammingError
|
|
@@ -78,7 +79,7 @@ class Database(object):
|
|
|
78
79
|
(columns, values) = self.parse_args(kwargs)
|
|
79
80
|
sql = "INSERT INTO {} ({}) VALUES ({})" \
|
|
80
81
|
.format(table_name, ','.join(columns), ', '.join(['?'] * len(columns)))
|
|
81
|
-
|
|
82
|
+
self.resilient_execute(conn, sql, values)
|
|
82
83
|
conn.commit()
|
|
83
84
|
|
|
84
85
|
@connect
|
|
@@ -89,7 +90,7 @@ class Database(object):
|
|
|
89
90
|
(columns, values) = self.parse_args(kwargs)
|
|
90
91
|
sql = "INSERT INTO {} ({}) VALUES ({})" \
|
|
91
92
|
.format(table_name, ','.join(columns), ', '.join(['?'] * len(columns)))
|
|
92
|
-
|
|
93
|
+
self.resilient_execute(conn, sql, values)
|
|
93
94
|
conn.commit()
|
|
94
95
|
|
|
95
96
|
@connect
|
|
@@ -98,7 +99,7 @@ class Database(object):
|
|
|
98
99
|
(columns, values) = self.parse_args(kwargs)
|
|
99
100
|
sql = "INSERT OR IGNORE INTO {} ({}) VALUES ({})" \
|
|
100
101
|
.format(table_name, ','.join(columns), ', '.join(['?'] * len(columns)))
|
|
101
|
-
|
|
102
|
+
self.resilient_execute(conn, sql, values)
|
|
102
103
|
conn.commit()
|
|
103
104
|
|
|
104
105
|
@connect
|
|
@@ -107,7 +108,7 @@ class Database(object):
|
|
|
107
108
|
(columns, values) = self.parse_args(kwargs)
|
|
108
109
|
sql = "INSERT OR REPLACE INTO {} ({}) VALUES ({})" \
|
|
109
110
|
.format(table_name, ','.join(columns), ', '.join(['?'] * len(columns)))
|
|
110
|
-
|
|
111
|
+
self.resilient_execute(conn, sql, values)
|
|
111
112
|
conn.commit()
|
|
112
113
|
|
|
113
114
|
def insert_update_one(self, table_name: str, **kwargs):
|
|
@@ -119,7 +120,7 @@ class Database(object):
|
|
|
119
120
|
(columns, values) = self.parse_args(kwargs)
|
|
120
121
|
sql = "INSERT OR IGNORE INTO {} ({}) VALUES ({})" \
|
|
121
122
|
.format(table_name, ','.join(columns), ', '.join(['?'] * len(columns)))
|
|
122
|
-
c =
|
|
123
|
+
c = self.resilient_execute(conn, sql, values)
|
|
123
124
|
|
|
124
125
|
status = {'inserted': c.rowcount, 'updated': 0}
|
|
125
126
|
|
|
@@ -135,7 +136,7 @@ class Database(object):
|
|
|
135
136
|
sql += "{}".format(', '.join([f'{col} = ?' for col in u_columns]))
|
|
136
137
|
if len(f_columns) > 0:
|
|
137
138
|
sql += " WHERE {}".format(f' and '.join([f'{col} = ?' for col in f_columns]))
|
|
138
|
-
c =
|
|
139
|
+
c = self.resilient_execute(conn, sql, tuple(u_values + f_values, ))
|
|
139
140
|
conn.commit()
|
|
140
141
|
|
|
141
142
|
status['updated'] = c.rowcount
|
|
@@ -155,12 +156,16 @@ class Database(object):
|
|
|
155
156
|
if len(columns) > 0:
|
|
156
157
|
sql += " WHERE {}".format(f' {operator} '.join([f'{col} = ?' for col in columns]))
|
|
157
158
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
data = []
|
|
160
|
+
with conn: # Transaction
|
|
161
|
+
cursor = self.resilient_execute(conn, sql, values)
|
|
162
|
+
if cursor.rowcount == 0:
|
|
163
|
+
return data
|
|
161
164
|
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
columns = cursor.description
|
|
166
|
+
data = [{columns[index][0]: column for index, column in enumerate(value)} for value in cursor.fetchall()]
|
|
167
|
+
|
|
168
|
+
return data
|
|
164
169
|
|
|
165
170
|
def select_first(self, table_name, **kwargs):
|
|
166
171
|
data = self.select(table_name, **kwargs)
|
|
@@ -170,11 +175,15 @@ class Database(object):
|
|
|
170
175
|
|
|
171
176
|
@connect
|
|
172
177
|
def select_raw(self, conn: Connection, sql: str, args: any):
|
|
173
|
-
cursor =
|
|
178
|
+
cursor = self.resilient_execute(conn, sql, tuple(args,))
|
|
174
179
|
if cursor.rowcount == 0:
|
|
175
180
|
return []
|
|
176
181
|
columns = cursor.description
|
|
177
|
-
|
|
182
|
+
data = []
|
|
183
|
+
with conn: # Transaction
|
|
184
|
+
data = [{columns[index][0]: column for index, column in enumerate(value)} for value in cursor.fetchall()]
|
|
185
|
+
|
|
186
|
+
return data
|
|
178
187
|
|
|
179
188
|
@connect
|
|
180
189
|
def select_count(self, conn: Connection, table_name, **kwargs) -> int:
|
|
@@ -187,10 +196,13 @@ class Database(object):
|
|
|
187
196
|
sql = f"SELECT count(*) FROM {table_name}"
|
|
188
197
|
if len(columns) > 0:
|
|
189
198
|
sql += " WHERE {}".format(f' {operator} '.join([f'{col} = ?' for col in columns]))
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
199
|
+
|
|
200
|
+
data = []
|
|
201
|
+
with conn: # Transaction
|
|
202
|
+
cursor = self.resilient_execute(conn, sql, values)
|
|
203
|
+
if cursor.rowcount == 0:
|
|
204
|
+
return 0
|
|
205
|
+
data = cursor.fetchone()
|
|
194
206
|
|
|
195
207
|
return int(data[0])
|
|
196
208
|
|
|
@@ -205,7 +217,7 @@ class Database(object):
|
|
|
205
217
|
sql = f"DELETE FROM {table_name}"
|
|
206
218
|
if len(columns) > 0:
|
|
207
219
|
sql += " WHERE {}".format(f' {operator} '.join([f'{col} = ?' for col in columns]))
|
|
208
|
-
|
|
220
|
+
self.resilient_execute(conn, sql, values)
|
|
209
221
|
conn.commit()
|
|
210
222
|
|
|
211
223
|
@connect
|
|
@@ -221,7 +233,7 @@ class Database(object):
|
|
|
221
233
|
sql += "{}".format(', '.join([f'{col} = ?' for col in u_columns]))
|
|
222
234
|
if len(f_columns) > 0:
|
|
223
235
|
sql += " WHERE {}".format(f' {operator} '.join([f'{col} = ?' for col in f_columns]))
|
|
224
|
-
|
|
236
|
+
self.resilient_execute(conn, sql, tuple(u_values + f_values, ))
|
|
225
237
|
conn.commit()
|
|
226
238
|
|
|
227
239
|
def get_constraints(self, conn: Connection) -> dict:
|
|
@@ -238,7 +250,7 @@ class Database(object):
|
|
|
238
250
|
' il.origin = "u" '
|
|
239
251
|
'ORDER BY table_name, key_name, ii.seqno')
|
|
240
252
|
|
|
241
|
-
cursor =
|
|
253
|
+
cursor = self.resilient_execute(conn, sql)
|
|
242
254
|
columns = cursor.description
|
|
243
255
|
db_scheme = [{columns[index][0]: column for index, column in enumerate(value)} for value in cursor.fetchall()]
|
|
244
256
|
|
|
@@ -333,7 +345,7 @@ class Database(object):
|
|
|
333
345
|
|
|
334
346
|
conn.commit()
|
|
335
347
|
|
|
336
|
-
#Must get the constraints
|
|
348
|
+
# Must get the constraints
|
|
337
349
|
self.get_constraints(conn)
|
|
338
350
|
|
|
339
351
|
def insert_history(self, source: str, data: str, stack_trace: str = ''):
|
|
@@ -363,3 +375,15 @@ class Database(object):
|
|
|
363
375
|
str
|
|
364
376
|
"""
|
|
365
377
|
return ''.join(k for k in input_string if k.isalnum() or k in '_-')
|
|
378
|
+
|
|
379
|
+
@classmethod
|
|
380
|
+
def resilient_execute(cls, conn: Connection, *args, **kwargs):
|
|
381
|
+
for _ in range(5):
|
|
382
|
+
try:
|
|
383
|
+
return conn.execute(*args, **kwargs)
|
|
384
|
+
except sqlite3.OperationalError as e:
|
|
385
|
+
if 'database is locked' in str(e).lower():
|
|
386
|
+
time.sleep(0.3)
|
|
387
|
+
else:
|
|
388
|
+
raise e
|
|
389
|
+
|