Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade the TagComponent to the KnowledgeComponent #7070

Merged
merged 45 commits into from
Oct 19, 2022
Merged
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
283e5c4
Add DB scheme for the `relation` field
drew2a Sep 27, 2022
b69b9d2
Add `HAS_TAG` condition to the `get_infohashes`
drew2a Oct 3, 2022
52e0416
Relax tag validation rules
drew2a Oct 3, 2022
601deb5
Fix get_infohashes with an absent tag
drew2a Oct 3, 2022
ef7dcfb
Ubuntu rules v1
drew2a Oct 3, 2022
8b12740
Ubuntu rules v2
drew2a Oct 3, 2022
87ade19
Ubuntu rules v3
drew2a Oct 3, 2022
664c5e4
Initial prototype for search result bundling
devos50 Sep 21, 2022
3ba7733
Improved prototype
devos50 Sep 23, 2022
8b4abe7
Further improvements
devos50 Sep 26, 2022
e33c9a1
Merge the backed changes and the GUI changes
drew2a Oct 3, 2022
46e26bc
Fix duplicate items bug
drew2a Oct 3, 2022
56636d2
Add Rules for Debian and Linux Mint
drew2a Oct 4, 2022
6ebd9d7
Move Enums to tag_db.py
drew2a Oct 5, 2022
e00ad06
Change tag_db scheme
drew2a Oct 6, 2022
ff8b474
Adopt the tag community
drew2a Oct 6, 2022
79d7fae
Adopt the REST
drew2a Oct 6, 2022
0476d6a
Adopt TagProcessor
drew2a Oct 6, 2022
a7554d7
Finish transfer to KG
drew2a Oct 6, 2022
7c7e875
Fix the upgrader's test
drew2a Oct 6, 2022
101f0cf
Fix tests
drew2a Oct 7, 2022
f996c1a
Linter
drew2a Oct 7, 2022
4fcab3b
Remove HAS
drew2a Oct 7, 2022
ab0c1c2
Add 'case_sensitive` argument
drew2a Oct 10, 2022
0002de5
Add subject type
drew2a Oct 10, 2022
62a886a
Rename `Predicate` to `ResourceType`
drew2a Oct 11, 2022
4b3e30b
Swap three digit linux version by two digit version
drew2a Oct 11, 2022
e7f64ec
Add documentation for tag_db
drew2a Oct 11, 2022
0571d97
Refactored snippet logic in search endpoint
devos50 Oct 11, 2022
166e469
Merge pull request #2 from devos50/kg_search_algorithm
drew2a Oct 11, 2022
aede0d1
Added tests for snippet generation when searching
devos50 Oct 11, 2022
d122fb9
Merge pull request #3 from devos50/search_endpoint_tests
drew2a Oct 11, 2022
c7e2373
Swap the relation from TORRENT to TITLE for Content Items
drew2a Oct 11, 2022
cbd1564
Minor GUI polish
devos50 Oct 11, 2022
52b122e
Merge pull request #4 from devos50/snippets_gui_polish
drew2a Oct 11, 2022
3a49a9e
Rename TagComponent to KnowledgeComponent
drew2a Oct 12, 2022
15fd1ed
Add migration
drew2a Oct 17, 2022
9d4b491
Change msg_id for the community
drew2a Oct 17, 2022
5a5d326
Refactor the Migration
drew2a Oct 17, 2022
4afd110
Add get_statements()
drew2a Oct 18, 2022
1c5d308
Renamed TagsEndpoint to KnowledgeEndpoint
devos50 Oct 18, 2022
d50e672
Updated REST API to process statements
devos50 Oct 18, 2022
8262795
Renamed /suggestions to /tag_suggestions
devos50 Oct 18, 2022
56e98ae
Merge pull request #5 from devos50/rename_tags_endpoint
drew2a Oct 18, 2022
901f4ee
Polish the code
drew2a Oct 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Improved prototype
  • Loading branch information
devos50 committed Oct 3, 2022
commit 3ba7733896248f0064d5e820afb74cb2a425a71d
Original file line number Diff line number Diff line change
@@ -16,10 +16,11 @@
from tribler.core.components.metadata_store.restapi.metadata_endpoint import MetadataEndpointBase
from tribler.core.components.metadata_store.restapi.metadata_schema import MetadataParameters, MetadataSchema
from tribler.core.components.restapi.rest.rest_endpoint import HTTP_BAD_REQUEST, RESTResponse
from tribler.core.components.tag.db.tag_db import TagDatabase
from tribler.core.components.tag.rules.tag_rules_processor import TagRulesProcessor
from tribler.core.utilities.unicode import hexlify
from tribler.core.utilities.utilities import froze_it, random_infohash
from tribler.core.utilities.utilities import froze_it


SNIPPETS_TO_SHOW = 1 # The number of snippets we return from the search results
MAX_TORRENTS_IN_SNIPPETS = 4 # The maximum number of torrents in each snippet


@froze_it
@@ -122,46 +123,58 @@ def search_db():
self._logger.exception("Error while performing DB search: %s: %s", type(e).__name__, e)
return RESTResponse(status=HTTP_BAD_REQUEST)

#self.add_tags_to_metadata_list(search_results, hide_xxx=sanitized["hide_xxx"])

count_for_content: Dict[str, int] = {}
most_popular_torrent_for_content: Dict[str, Dict] = {}
for search_result in search_results:
if search_result["infohash"] in self.torrent_to_content:
content_id = self.torrent_to_content[search_result["infohash"]]
if content_id not in count_for_content:
count_for_content[content_id] = 0
if content_id not in most_popular_torrent_for_content:
most_popular_torrent_for_content[content_id] = search_result
else:
if search_result["num_seeders"] > most_popular_torrent_for_content[content_id]["num_seeders"]:
most_popular_torrent_for_content[content_id] = search_result
count_for_content[content_id] += 1

# Remove search results that are tied to content
print("Before: %d" % len(search_results))
search_results = [search_result for search_result in search_results if search_result["infohash"] not in self.torrent_to_content]
print("After: %d" % len(search_results))
print(search_results)

# Add the content
content_list: List[Dict] = []
for content_id, content_info in self.content.items():
if content_id in count_for_content:
content = {
self.add_tags_to_metadata_list(search_results, hide_xxx=sanitized["hide_xxx"])

# Build snippets
# TODO we probably need to do another database query to get ALL possible content that should go in the snippet
if sanitized["first"] == 1: # Only show a snippet on top
content_to_torrents: Dict[str, list] = {}
most_popular_torrents_for_content: Dict[str, List] = {}
for search_result in search_results:
if search_result["infohash"] in self.torrent_to_content:
content_id = self.torrent_to_content[search_result["infohash"]]
if content_id not in content_to_torrents:
content_to_torrents[content_id] = []
content_to_torrents[content_id].append(search_result)

if content_id not in most_popular_torrents_for_content:
most_popular_torrents_for_content[content_id] = []
most_popular_torrents_for_content[content_id].append(search_result)

# Sort by popularity
for torrents_list in most_popular_torrents_for_content.values():
torrents_list.sort(key=lambda x: x["num_seeders"], reverse=True)

# Determine the most popular content item - this is the one we show
sorted_content_info = list(content_to_torrents.items())
sorted_content_info.sort(key=lambda x: len(x[1]), reverse=True)
snippets = []
torrents_in_snippets = []

for content_info in sorted_content_info:
content_id = content_info[0]
content = self.content[content_id]
torrents_in_snippet = most_popular_torrents_for_content[content_id][:MAX_TORRENTS_IN_SNIPPETS]

snippet = {
"type": CONTENT,
"infohash": content_id,
"category": "",
"name": "%s (%s)" % (content_info[0], content_info[1]),
"torrents": count_for_content[content_id],
"popular": most_popular_torrent_for_content[content_id]
"name": "%s (%s)" % (content[0], content[1]),
"torrents": len(content_info[1]),
"torrents_in_snippet": torrents_in_snippet
}
content_list.insert(0, content)
torrents_in_snippets += torrents_in_snippet
snippets.append(snippet)

snippets = snippets[:SNIPPETS_TO_SHOW]

# Sort based on the number of subitems
content_list.sort(key=lambda x: x["torrents"], reverse=True)
# Remove search results that are displayed in a snippet
for snippet in snippets:
torrents_in_snippets += content_to_torrents[snippet["infohash"]]

search_results = content_list + search_results
search_results = [search_result for search_result in search_results if (search_result["infohash"] not in torrents_in_snippets)]
search_results = snippets + search_results

response_dict = {
"results": search_results,
21 changes: 12 additions & 9 deletions src/tribler/gui/widgets/lazytableview.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,8 @@
from PyQt5.QtWidgets import QAbstractItemView, QApplication, QHeaderView, QLabel, QTableView

from tribler.core.components.metadata_store.db.orm_bindings.channel_node import LEGACY_ENTRY
from tribler.core.components.metadata_store.db.serialization import CHANNEL_TORRENT, COLLECTION_NODE, REGULAR_TORRENT
from tribler.core.components.metadata_store.db.serialization import CHANNEL_TORRENT, COLLECTION_NODE, REGULAR_TORRENT, \
CONTENT

from tribler.gui.defs import COMMIT_STATUS_COMMITTED
from tribler.gui.dialogs.addtagsdialog import AddTagsDialog
@@ -51,6 +52,7 @@ class TriblerContentTableView(QTableView):

channel_clicked = pyqtSignal(dict)
torrent_clicked = pyqtSignal(dict)
content_clicked = pyqtSignal(dict)
torrent_doubleclicked = pyqtSignal(dict)
edited_tags = pyqtSignal(dict)

@@ -127,10 +129,10 @@ def mousePressEvent(self, event: QMouseEvent) -> None:

# Check if we are clicking the 'popular content' button
if index in index.model().download_popular_content_rects:
rect = index.model().download_popular_content_rects[index]
if rect.contains(event.pos()) and event.button() != Qt.RightButton:
should_select_row = False
self.on_download_popular_torrent_clicked(index)
for torrent_index, rect in enumerate(index.model().download_popular_content_rects[index]):
if rect.contains(event.pos()) and event.button() != Qt.RightButton:
should_select_row = False
self.on_download_popular_torrent_clicked(index, torrent_index)

if should_select_row:
super().mousePressEvent(event)
@@ -199,9 +201,9 @@ def on_edit_tags_clicked(self, index: QModelIndex) -> None:
self.add_tags_dialog.show()
connect(self.add_tags_dialog.save_button_clicked, self.save_edited_tags)

def on_download_popular_torrent_clicked(self, index: QModelIndex) -> None:
def on_download_popular_torrent_clicked(self, index: QModelIndex, torrent_index: int) -> None:
data_item = index.model().data_items[index.row()]
self.start_download_from_dataitem(data_item)
self.start_download_from_dataitem(data_item["torrents_in_snippet"][torrent_index])

def on_table_item_clicked(self, item, doubleclick=False):
# We don't want to trigger the click-based events on, say, Ctrl-click based selection
@@ -219,8 +221,9 @@ def on_table_item_clicked(self, item, doubleclick=False):
# Safely determine if the thing is a channel. A little bit hackish
if data_item.get('type') in [CHANNEL_TORRENT, COLLECTION_NODE]:
self.channel_clicked.emit(data_item)

if data_item.get('type') == REGULAR_TORRENT:
elif data_item.get("type") == CONTENT:
self.content_clicked.emit(data_item)
elif data_item.get('type') == REGULAR_TORRENT:
if not doubleclick:
self.torrent_clicked.emit(data_item)
else:
120 changes: 72 additions & 48 deletions src/tribler/gui/widgets/tablecontentdelegate.py
Original file line number Diff line number Diff line change
@@ -46,7 +46,8 @@
TRIBLER_PALETTE.setColor(QPalette.Highlight, TRIBLER_ORANGE)

DEFAULT_ROW_HEIGHT = 30
CONTENT_ROW_HEIGHT = 110
CONTENT_ROW_HEIGHT = 72
TORRENT_IN_SNIPPET_HEIGHT = 44
MAX_TAGS_TO_SHOW = 10


@@ -140,7 +141,7 @@ def __init__(self, parent=None):
self.font_metrics = None

self.hovering_over_tag_edit_button = False
self.hovering_over_download_popular_torrent_button = False
self.hovering_over_download_popular_torrent_button: int = -1

# TODO: restore this behavior, so there is really some tolerance zone!
# We have to control if mouse is in the buttons box to add some tolerance for vertical mouse
@@ -173,7 +174,9 @@ def sizeHint(self, _, index: QModelIndex) -> QSize:
data_item = index.model().data_items[index.row()]

if data_item["type"] == CONTENT:
return QSize(0, CONTENT_ROW_HEIGHT)
torrents_in_snippet = len(data_item["torrents_in_snippet"])
height = CONTENT_ROW_HEIGHT + TORRENT_IN_SNIPPET_HEIGHT * torrents_in_snippet
return QSize(0, height)

tags_disabled = self.get_bool_gui_setting("disable_tags")
if data_item["type"] != REGULAR_TORRENT or tags_disabled:
@@ -228,16 +231,16 @@ def on_mouse_moved(self, pos, index):
self.hovering_over_tag_edit_button = new_hovering_state

# Check if we are hovering over the download popular torrents button
new_hovering_state = False
new_hovering_state = -1
if (
self.hover_index != self.no_index
and self.hover_index.column() == index.model().column_position[Column.NAME]
):
if index in index.model().download_popular_content_rects:
rect = index.model().download_popular_content_rects[index]
if rect.contains(pos):
QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor))
new_hovering_state = True
for ind, rect in enumerate(index.model().download_popular_content_rects[index]):
if rect.contains(pos):
QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor))
new_hovering_state = ind

if new_hovering_state != self.hovering_over_download_popular_torrent_button:
self.redraw_required.emit(index, False)
@@ -248,7 +251,7 @@ def on_mouse_moved(self, pos, index):

def on_mouse_left(self) -> None:
self.hovering_over_tag_edit_button = False
self.hovering_over_download_popular_torrent_button = False
self.hovering_over_download_popular_torrent_button = -1

@staticmethod
def split_rect_into_squares(r, buttons):
@@ -373,22 +376,21 @@ def draw_title_and_tags(
) -> None:
painter.setRenderHint(QPainter.Antialiasing, True)
title_text_pos = option.rect.topLeft()
title_text_height = (CONTENT_ROW_HEIGHT - 20 - 2) if data_item["type"] == CONTENT else 28
title_text_height = 60 if data_item["type"] == CONTENT else 28
title_text_y = (title_text_pos.y() + 10) if data_item["type"] == CONTENT else title_text_pos.y()
title_text_x = (title_text_pos.x() + 56) if data_item["type"] == CONTENT else (title_text_pos.x() + 6)
painter.setPen(Qt.white)

title_text = data_item["name"] if data_item["type"] != CONTENT else ("%s - %d items" % (data_item["name"], data_item["torrents"]))

if data_item["type"] == CONTENT:
# Increase the font size
font = painter.font()
font.setPixelSize(15)
painter.setFont(font)

painter.drawText(
QRectF(title_text_x, title_text_pos.y(), option.rect.width() - 6, title_text_height),
QRectF(title_text_x, title_text_y, option.rect.width() - 6, title_text_height),
Qt.AlignVCenter,
title_text,
data_item["name"],
)

if data_item["type"] == CONTENT:
@@ -401,7 +403,7 @@ def draw_title_and_tags(
if data_item["type"] == CONTENT:
painter.setPen(QColor(get_color(data_item["name"])))
path = QPainterPath()
rect = QRectF(option.rect.x(), option.rect.topLeft().y() + 10, 40, CONTENT_ROW_HEIGHT - 50)
rect = QRectF(option.rect.x(), option.rect.topLeft().y() + 10, 40, 60)
path.addRect(rect)
painter.fillPath(path, QColor(get_color(data_item["name"])))
painter.drawPath(path)
@@ -420,17 +422,30 @@ def draw_title_and_tags(
font.setPixelSize(13)
painter.setFont(font)

download_popular_torrent_button_hovered = self.hovering_over_download_popular_torrent_button and self.hover_index == index
painter.setPen(QColor(QColor(TRIBLER_ORANGE) if download_popular_torrent_button_hovered else "#aaa"))
popular_torrent_text_rect = QRectF(option.rect.x(), option.rect.topLeft().y() + 74, option.rect.width() - 6, 26)
painter.drawText(
popular_torrent_text_rect,
Qt.AlignVCenter,
"Most popular: %s (S%d L%d)" % (data_item["popular"]["name"],
data_item["popular"]["num_seeders"],
data_item["popular"]["num_leechers"]),
)
index.model().download_popular_content_rects[index] = popular_torrent_text_rect
snippets_y = option.rect.topLeft().y() + 60

font = painter.font()
font.setPixelSize(15)
painter.setFont(font)

index.model().download_popular_content_rects[index] = []
for torrent_ind, torrent_in_snippet in enumerate(data_item["torrents_in_snippet"]):
is_hovering = self.hovering_over_download_popular_torrent_button == torrent_ind and self.hover_index == index
painter.setPen(QColor(QColor(TRIBLER_ORANGE) if is_hovering else "#ccc"))

torrent_in_snippet_rect = QRectF(title_text_x, snippets_y, option.rect.width() - 6, TORRENT_IN_SNIPPET_HEIGHT)
painter.drawText(
torrent_in_snippet_rect,
Qt.AlignVCenter,
torrent_in_snippet["name"],
)

index.model().download_popular_content_rects[index].append(torrent_in_snippet_rect)
snippets_y += TORRENT_IN_SNIPPET_HEIGHT

font = painter.font()
font.setPixelSize(13)
painter.setFont(font)

cur_tag_x = option.rect.x() + 6
cur_tag_y = option.rect.y() + TAG_TOP_MARGIN
@@ -572,7 +587,7 @@ def draw_health_column(self, painter, option, index, data_item):
self.paint_empty_background(painter, option)

# This dumb check is required because some endpoints do not return entry type
if 'type' not in data_item or data_item['type'] == REGULAR_TORRENT:
if 'type' not in data_item or data_item['type'] in [REGULAR_TORRENT]:
self.health_status_widget.paint(painter, option.rect, index, hover=index == self.hover_index)

return True
@@ -825,25 +840,33 @@ class HealthStatusDisplay(QObject):
def paint(self, painter, rect, index, hover=False):
data_item = index.model().data_items[index.row()]

if 'health' not in data_item or data_item['health'] == "updated":
data_item['health'] = get_health(
data_item['num_seeders'], data_item['num_leechers'], data_item['last_tracker_check']
)
health = data_item['health']

# ----------------
# |b---b| |
# |b|i|b| 0S 0L |
# |b---b| |
# ----------------

r = rect

if data_item["type"] == REGULAR_TORRENT:
if 'health' not in data_item or data_item['health'] == "updated":
data_item['health'] = get_health(
data_item['num_seeders'], data_item['num_leechers'], data_item['last_tracker_check']
)
health = data_item['health']

panel_y = rect.y() + rect.height() / 2 - 5
self.paint_elements(painter, rect, panel_y, health, data_item, hover)
elif data_item["type"] == CONTENT:
for ind, torrent_in_snippet in enumerate(data_item["torrents_in_snippet"]):
panel_y = rect.topLeft().y() + 60 + TORRENT_IN_SNIPPET_HEIGHT / 2 + TORRENT_IN_SNIPPET_HEIGHT * ind - 6
health = get_health(torrent_in_snippet['num_seeders'], torrent_in_snippet['num_leechers'], torrent_in_snippet['last_tracker_check'])
self.paint_elements(painter, rect, panel_y, health, torrent_in_snippet, hover, draw_health_text=False)

def paint_elements(self, painter, rect, panel_y, health, data_item, hover=False, draw_health_text=True):
painter.save()

# Indicator ellipse rectangle
y = int(r.top() + (r.height() - self.indicator_side) // 2)
x = r.left() + self.indicator_border
y = panel_y # int(r.top() + (r.height() - self.indicator_side) // 2)
x = rect.left() + self.indicator_border
w = self.indicator_side
h = self.indicator_side
indicator_rect = QRect(x, y, w, h)
@@ -854,22 +877,23 @@ def paint(self, painter, rect, index, hover=False):
painter.drawEllipse(indicator_rect)

x = indicator_rect.left() + indicator_rect.width() + 2 * self.indicator_border
y = r.top()
w = r.width() - indicator_rect.width() - 2 * self.indicator_border
h = r.height()
y = panel_y
w = rect.width() - indicator_rect.width() - 2 * self.indicator_border
h = 10
text_box = QRect(x, y, w, h)

# Paint status text, if necessary
if health in (HEALTH_CHECKING, HEALTH_UNCHECKED, HEALTH_ERROR):
txt = health
else:
seeders = int(data_item['num_seeders'])
leechers = int(data_item['num_leechers'])
if draw_health_text:
if health in (HEALTH_CHECKING, HEALTH_UNCHECKED, HEALTH_ERROR):
txt = health
else:
seeders = int(data_item['num_seeders'])
leechers = int(data_item['num_leechers'])

txt = 'S' + str(seeders) + ' L' + str(leechers)
txt = 'S' + str(seeders) + ' L' + str(leechers)

color = TRIBLER_PALETTE.light().color() if hover else TRIBLER_NEUTRAL
draw_text(painter, text_box, txt, color=color)
color = TRIBLER_PALETTE.light().color() if hover else TRIBLER_NEUTRAL
draw_text(painter, text_box, txt, color=color)
painter.restore()


Loading