Skip to content

Commit 2c66340

Browse files
committed
feat: speed up nice_list filter for analysis summary
1 parent d5466a8 commit 2c66340

File tree

8 files changed

+62
-18
lines changed

8 files changed

+62
-18
lines changed

src/storage/db_interface_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def get_file_tree_path(self, uid: str, root_uid: str | None = None) -> list[list
214214
return self.get_file_tree_path_for_uid_list([uid], root_uid=root_uid).get(uid, [])
215215

216216
def get_file_tree_path_for_uid_list(
217-
self, uid_list: list[str], root_uid: str | None = None
217+
self, uid_list: Iterable[str], root_uid: str | None = None
218218
) -> dict[str, list[list[str]]]:
219219
"""
220220
Generate all file paths for a list of UIDs `uid_list`. A path is a list of UIDs representing the path from root

src/storage/db_interface_frontend.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os.path
44
import re
55
from pathlib import Path
6-
from typing import Any, NamedTuple, Optional
6+
from typing import Any, Iterable, NamedTuple, Optional
77

88
from sqlalchemy import Column, Integer, cast, func, or_, select
99

@@ -75,24 +75,50 @@ def _get_hid_fo(self, uid: str, root_uid: str | None = None) -> str | None:
7575

7676
# --- "nice list" ---
7777

78-
def get_data_for_nice_list(self, uid_list: list[str], root_uid: str | None) -> list[dict]:
78+
def _get_some_path_for_uid_list(self, uid_list: Iterable[str], root_uid: str | None = None) -> dict[str, str]:
7979
with self.get_read_only_session() as session:
80-
mime_dict = self._get_mime_types_for_uid_list(session, uid_list)
81-
query = select(FileObjectEntry.uid, FileObjectEntry.size, FileObjectEntry.file_name).filter(
82-
FileObjectEntry.uid.in_(uid_list)
80+
query = (
81+
select(VirtualFilePath.file_uid, VirtualFilePath.file_path)
82+
.filter(VirtualFilePath.file_uid.in_(uid_list))
83+
.distinct(VirtualFilePath.file_uid) # only one path per file
84+
)
85+
if root_uid: # if root_uid is set, only return paths contained in that FW
86+
query = query.join(fw_files_table, VirtualFilePath.file_uid == fw_files_table.c.file_uid).filter(
87+
fw_files_table.c.root_uid == root_uid
88+
)
89+
return {uid: path for uid, path in session.execute(query)} # noqa: C416 # dict() does not work here
90+
91+
def get_data_for_nice_list(
92+
self, uid_list: Iterable[str], root_uid: str | None, include_vfp: bool = True
93+
) -> list[dict]:
94+
with self.get_read_only_session() as session:
95+
query = (
96+
select(
97+
FileObjectEntry.uid,
98+
FileObjectEntry.size,
99+
FileObjectEntry.file_name,
100+
AnalysisEntry.result.op('->>')('mime'),
101+
)
102+
.filter(FileObjectEntry.uid.in_(uid_list))
103+
.outerjoin(AnalysisEntry, AnalysisEntry.uid == FileObjectEntry.uid)
104+
.filter(AnalysisEntry.plugin == 'file_type')
83105
)
84-
file_tree_data = self.get_file_tree_path_for_uid_list(uid_list, root_uid=root_uid)
106+
path_dict = self._get_some_path_for_uid_list(uid_list)
107+
85108
nice_list_data = [
86109
{
87110
'uid': uid,
88111
'size': size,
89-
'file_name': file_name,
90-
'mime-type': mime_dict.get(uid, 'file-type-plugin/not-run-yet'),
91-
'current_virtual_path': file_tree_data[uid],
112+
'file_name': path_dict.get(uid, file_name),
113+
'mime-type': mime or 'N/A',
92114
}
93-
for uid, size, file_name in session.execute(query)
115+
for uid, size, file_name, mime in session.execute(query)
94116
]
95-
self._replace_uids_in_nice_list(nice_list_data, root_uid)
117+
if include_vfp:
118+
file_tree_data = self.get_file_tree_path_for_uid_list(uid_list, root_uid=root_uid)
119+
for dict_ in nice_list_data:
120+
dict_['current_virtual_path'] = file_tree_data.get(dict_['uid'], [[dict_['file_name']]])
121+
self._replace_uids_in_nice_list(nice_list_data, root_uid)
96122
return nice_list_data
97123

98124
def _replace_uids_in_nice_list(self, nice_list_data: list[dict], root_uid: str):

src/test/common_helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def uid_list_exists(self, uid_list):
207207
def all_uids_found_in_database(self, uid_list):
208208
return True
209209

210-
def get_data_for_nice_list(self, input_data, root_uid):
210+
def get_data_for_nice_list(self, input_data, root_uid, include_vfp=None):
211211
return [NICE_LIST_DATA]
212212

213213
@staticmethod

src/test/integration/storage/test_db_interface_frontend.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ def test_get_data_for_nice_list(frontend_db, backend_db):
9090
assert nice_list_data[0]['current_virtual_path'][0] == expected_hid, 'UID should be replaced with HID'
9191
assert nice_list_data[1]['current_virtual_path'][0] == f'{expected_hid} | /file/path'
9292

93+
nice_list_data = frontend_db.get_data_for_nice_list(uid_list, uid_list[0], include_vfp=False)
94+
assert len(nice_list_data) == 2
95+
assert sorted(nice_list_data[0].keys()) == ['file_name', 'mime-type', 'size', 'uid']
96+
9397

9498
def test_get_device_class_list(frontend_db, backend_db):
9599
insert_test_fw(backend_db, 'fw1', device_class='class1')

src/web_interface/components/ajax_routes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,16 @@ def ajax_get_hex_preview(self, uid: str, offset: int, length: int) -> str:
118118
def ajax_get_summary(self, uid, selected_analysis):
119119
with get_shared_session(self.db.frontend) as frontend_db:
120120
firmware = frontend_db.get_object(uid, analysis_filter=selected_analysis)
121-
summary_of_included_files = frontend_db.get_summary(firmware, selected_analysis)
121+
summary_of_included_files = frontend_db.get_summary(firmware, selected_analysis) or {}
122+
all_uids = {uid for uid_list in summary_of_included_files.values() for uid in uid_list}
123+
nice_list_data = self.db.frontend.get_data_for_nice_list(all_uids, uid, False)
122124
root_uid = uid if isinstance(firmware, Firmware) else frontend_db.get_root_uid(uid)
123125
return render_template(
124126
'summary.html',
125127
summary_of_included_files=summary_of_included_files,
126128
root_uid=root_uid,
127129
selected_analysis=selected_analysis,
130+
nice_list_data=nice_list_data,
128131
)
129132

130133
@roles_accepted(*PRIVILEGES['status'])

src/web_interface/components/jinja_filter.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,23 @@ def _filter_replace_uid_with_hid_link(self, input_data, root_uid=None):
7070
content = content.replace(uid, f'<a style="text-reset" href="/analysis/{uid}/ro/{root_uid}">{hid}</a>')
7171
return content
7272

73-
def _filter_nice_uid_list(self, uids, root_uid=None, selected_analysis=None, filename_only=False):
73+
def _filter_nice_uid_list(
74+
self,
75+
uids,
76+
root_uid=None,
77+
selected_analysis=None,
78+
filename_only=False,
79+
nice_list_data: list[dict] | None = None,
80+
):
7481
root_uid = none_to_none(root_uid)
7582
if not is_list_of_uids(uids):
7683
return uids
7784

78-
analyzed_uids = self.db.frontend.get_data_for_nice_list(uids, root_uid)
85+
if nice_list_data:
86+
# if multiple "nice lists" are rendered at once, nice_list_data should be prefetched for all files
87+
analyzed_uids = [d for d in nice_list_data if d['uid'] in uids]
88+
else:
89+
analyzed_uids = self.db.frontend.get_data_for_nice_list(uids, root_uid, include_vfp=not filename_only)
7990
number_of_unanalyzed_files = len(uids) - len(analyzed_uids)
8091
first_item = analyzed_uids.pop(0)
8192

src/web_interface/templates/generic_view/file_object_macros.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% macro file_information_span(file_object, root_uid, selected_analysis=None, filename_only=False) %}
22
{% set selected_analysis_url = selected_analysis + '/' if selected_analysis else '' %}
33
<a href="/analysis/{{ file_object.uid }}/{{ selected_analysis_url }}ro/{{ root_uid }}">
4-
{% if filename_only %}
4+
{% if filename_only or "current_virtual_path" not in file_object %}
55
<h6>{{ file_object.file_name | safe }}</h6>
66
{% else %}
77
<h6>{{ file_object.current_virtual_path[0] | safe }}</h6>

src/web_interface/templates/summary.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<tr>
1818
<td>{{ item | wordwrap(width=60, break_long_words=True) }}</td>
1919
<td>
20-
{{ summary_of_included_files[item] | nice_uid_list(root_uid=root_uid, selected_analysis=selected_analysis) | safe }}
20+
{{ summary_of_included_files[item] | nice_uid_list(root_uid=root_uid, selected_analysis=selected_analysis, nice_list_data=nice_list_data) | safe }}
2121
</td>
2222
</tr>
2323
{% endfor %}

0 commit comments

Comments
 (0)