relevance sort
This commit is contained in:
parent
c6dd9a73f5
commit
35e7dbb060
5 changed files with 1502 additions and 6 deletions
Binary file not shown.
1323
logs/app.log
1323
logs/app.log
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
|
|
@ -3674,7 +3674,8 @@ class DownloadsPage(QWidget):
|
||||||
# Initialize filter and sort state
|
# Initialize filter and sort state
|
||||||
self.current_filter = "all" # "all", "albums", "singles"
|
self.current_filter = "all" # "all", "albums", "singles"
|
||||||
self.current_format_filter = "all" # "all", "flac", "mp3", "ogg", "aac", "wma"
|
self.current_format_filter = "all" # "all", "flac", "mp3", "ogg", "aac", "wma"
|
||||||
self.current_sort = "quality" # "quality", "size", "name", "uploader", "bitrate", "duration", "availability", "speed"
|
self.current_sort = "relevance" # "relevance", "quality", "size", "name", "uploader", "bitrate", "duration", "availability", "speed"
|
||||||
|
self.current_search_query = "" # Store search query for relevance calculation
|
||||||
|
|
||||||
# Type filter buttons
|
# Type filter buttons
|
||||||
self.filter_all_btn = QPushButton("All")
|
self.filter_all_btn = QPushButton("All")
|
||||||
|
|
@ -3777,6 +3778,7 @@ class DownloadsPage(QWidget):
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Sort buttons
|
# Sort buttons
|
||||||
|
self.sort_relevance_btn = QPushButton("Relevance")
|
||||||
self.sort_quality_btn = QPushButton("Quality")
|
self.sort_quality_btn = QPushButton("Quality")
|
||||||
self.sort_size_btn = QPushButton("Size")
|
self.sort_size_btn = QPushButton("Size")
|
||||||
self.sort_name_btn = QPushButton("Name")
|
self.sort_name_btn = QPushButton("Name")
|
||||||
|
|
@ -3788,6 +3790,7 @@ class DownloadsPage(QWidget):
|
||||||
|
|
||||||
# Store sort buttons for easy access
|
# Store sort buttons for easy access
|
||||||
self.sort_buttons = {
|
self.sort_buttons = {
|
||||||
|
"relevance": self.sort_relevance_btn,
|
||||||
"quality": self.sort_quality_btn,
|
"quality": self.sort_quality_btn,
|
||||||
"size": self.sort_size_btn,
|
"size": self.sort_size_btn,
|
||||||
"name": self.sort_name_btn,
|
"name": self.sort_name_btn,
|
||||||
|
|
@ -3799,6 +3802,7 @@ class DownloadsPage(QWidget):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Connect sort button signals
|
# Connect sort button signals
|
||||||
|
self.sort_relevance_btn.clicked.connect(lambda: self.set_sort("relevance"))
|
||||||
self.sort_quality_btn.clicked.connect(lambda: self.set_sort("quality"))
|
self.sort_quality_btn.clicked.connect(lambda: self.set_sort("quality"))
|
||||||
self.sort_size_btn.clicked.connect(lambda: self.set_sort("size"))
|
self.sort_size_btn.clicked.connect(lambda: self.set_sort("size"))
|
||||||
self.sort_name_btn.clicked.connect(lambda: self.set_sort("name"))
|
self.sort_name_btn.clicked.connect(lambda: self.set_sort("name"))
|
||||||
|
|
@ -3812,9 +3816,10 @@ class DownloadsPage(QWidget):
|
||||||
for btn_key, btn in self.sort_buttons.items():
|
for btn_key, btn in self.sort_buttons.items():
|
||||||
btn.setFixedHeight(28)
|
btn.setFixedHeight(28)
|
||||||
btn.setMinimumWidth(55)
|
btn.setMinimumWidth(55)
|
||||||
self.update_filter_button_style(btn, btn_key == "quality")
|
self.update_filter_button_style(btn, btn_key == "relevance")
|
||||||
|
|
||||||
sort_row.addWidget(sort_label)
|
sort_row.addWidget(sort_label)
|
||||||
|
sort_row.addWidget(self.sort_relevance_btn)
|
||||||
sort_row.addWidget(self.sort_quality_btn)
|
sort_row.addWidget(self.sort_quality_btn)
|
||||||
sort_row.addWidget(self.sort_size_btn)
|
sort_row.addWidget(self.sort_size_btn)
|
||||||
sort_row.addWidget(self.sort_name_btn)
|
sort_row.addWidget(self.sort_name_btn)
|
||||||
|
|
@ -3954,7 +3959,9 @@ class DownloadsPage(QWidget):
|
||||||
|
|
||||||
print(f"DEBUG: Sorting {len(results)} results by {self.current_sort}")
|
print(f"DEBUG: Sorting {len(results)} results by {self.current_sort}")
|
||||||
|
|
||||||
if self.current_sort == "quality":
|
if self.current_sort == "relevance":
|
||||||
|
sorted_results = sorted(results, key=self._sort_by_relevance, reverse=True)
|
||||||
|
elif self.current_sort == "quality":
|
||||||
sorted_results = sorted(results, key=self._sort_by_quality, reverse=True)
|
sorted_results = sorted(results, key=self._sort_by_quality, reverse=True)
|
||||||
elif self.current_sort == "size":
|
elif self.current_sort == "size":
|
||||||
sorted_results = sorted(results, key=self._sort_by_size, reverse=True)
|
sorted_results = sorted(results, key=self._sort_by_size, reverse=True)
|
||||||
|
|
@ -3987,6 +3994,171 @@ class DownloadsPage(QWidget):
|
||||||
|
|
||||||
return sorted_results
|
return sorted_results
|
||||||
|
|
||||||
|
def _sort_by_relevance(self, result):
|
||||||
|
"""Sort by relevance score combining search matching, quality, completeness, and availability"""
|
||||||
|
if not hasattr(self, 'current_search_query') or not self.current_search_query:
|
||||||
|
# Fallback to quality score if no search query
|
||||||
|
return self._sort_by_quality(result)
|
||||||
|
|
||||||
|
score = 0.0
|
||||||
|
query_terms = self.current_search_query.lower().split()
|
||||||
|
|
||||||
|
# 1. Search Term Matching (40% weight - 0.4 max)
|
||||||
|
search_score = self._calculate_search_match_score(result, query_terms)
|
||||||
|
score += search_score * 0.4
|
||||||
|
|
||||||
|
# 2. Quality Score (25% weight - 0.25 max)
|
||||||
|
quality_score = self._sort_by_quality(result)
|
||||||
|
score += quality_score * 0.25
|
||||||
|
|
||||||
|
# 3. File Completeness (20% weight - 0.2 max)
|
||||||
|
completeness_score = self._calculate_completeness_score(result)
|
||||||
|
score += completeness_score * 0.2
|
||||||
|
|
||||||
|
# 4. User Reliability (10% weight - 0.1 max)
|
||||||
|
reliability_score = self._calculate_reliability_score(result)
|
||||||
|
score += reliability_score * 0.1
|
||||||
|
|
||||||
|
# 5. File Freshness (5% weight - 0.05 max)
|
||||||
|
freshness_score = self._calculate_freshness_score(result)
|
||||||
|
score += freshness_score * 0.05
|
||||||
|
|
||||||
|
print(f"DEBUG: Relevance score for {getattr(result, 'filename', getattr(result, 'album_title', 'Unknown'))}: {score:.3f}")
|
||||||
|
return score
|
||||||
|
|
||||||
|
def _calculate_search_match_score(self, result, query_terms):
|
||||||
|
"""Calculate search term matching score (0.0 to 1.0)"""
|
||||||
|
if not query_terms:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# Get searchable text
|
||||||
|
searchable_text = ""
|
||||||
|
if hasattr(result, 'album_title'): # AlbumResult
|
||||||
|
searchable_text = f"{result.album_title} {result.artist or ''}"
|
||||||
|
elif hasattr(result, 'filename'): # TrackResult
|
||||||
|
searchable_text = f"{result.filename} {result.artist or ''} {result.title or ''} {result.album or ''}"
|
||||||
|
|
||||||
|
searchable_text = searchable_text.lower()
|
||||||
|
full_query = self.current_search_query.lower()
|
||||||
|
|
||||||
|
score = 0.0
|
||||||
|
|
||||||
|
# Exact match bonus (1.0 points)
|
||||||
|
if full_query in searchable_text:
|
||||||
|
score += 1.0
|
||||||
|
|
||||||
|
# Individual term matches (0.5 points each)
|
||||||
|
term_matches = 0
|
||||||
|
for term in query_terms:
|
||||||
|
if term in searchable_text:
|
||||||
|
term_matches += 1
|
||||||
|
score += (term_matches / len(query_terms)) * 0.5
|
||||||
|
|
||||||
|
# Position bonus (0.3 points if terms appear early)
|
||||||
|
position_bonus = 0.0
|
||||||
|
for term in query_terms:
|
||||||
|
pos = searchable_text.find(term)
|
||||||
|
if pos >= 0:
|
||||||
|
# Earlier positions get higher bonus
|
||||||
|
position_bonus += max(0, (50 - pos) / 50) * 0.3
|
||||||
|
score += position_bonus / len(query_terms)
|
||||||
|
|
||||||
|
return min(score, 1.0)
|
||||||
|
|
||||||
|
def _calculate_completeness_score(self, result):
|
||||||
|
"""Calculate file completeness score (0.0 to 1.0)"""
|
||||||
|
score = 0.0
|
||||||
|
|
||||||
|
if hasattr(result, 'tracks'): # AlbumResult
|
||||||
|
# Complete albums bonus
|
||||||
|
track_count = len(result.tracks)
|
||||||
|
if 8 <= track_count <= 20:
|
||||||
|
score += 0.8
|
||||||
|
elif 5 <= track_count <= 25:
|
||||||
|
score += 0.6
|
||||||
|
elif track_count > 25:
|
||||||
|
score += 0.4
|
||||||
|
else:
|
||||||
|
score += 0.2
|
||||||
|
|
||||||
|
# Album metadata bonus
|
||||||
|
if result.artist and result.album_title:
|
||||||
|
score += 0.2
|
||||||
|
else: # TrackResult
|
||||||
|
# Popular song length bonus
|
||||||
|
if hasattr(result, 'duration') and result.duration:
|
||||||
|
if 180 <= result.duration <= 300: # 3-5 minutes
|
||||||
|
score += 0.6
|
||||||
|
elif 120 <= result.duration <= 360: # 2-6 minutes
|
||||||
|
score += 0.4
|
||||||
|
else:
|
||||||
|
score += 0.2
|
||||||
|
else:
|
||||||
|
score += 0.3 # Default if no duration
|
||||||
|
|
||||||
|
# Track metadata bonus
|
||||||
|
if result.artist and result.title:
|
||||||
|
score += 0.4
|
||||||
|
elif result.artist or result.title:
|
||||||
|
score += 0.2
|
||||||
|
|
||||||
|
return min(score, 1.0)
|
||||||
|
|
||||||
|
def _calculate_reliability_score(self, result):
|
||||||
|
"""Calculate user reliability score (0.0 to 1.0)"""
|
||||||
|
score = 0.0
|
||||||
|
|
||||||
|
# High upload speed bonus
|
||||||
|
if hasattr(result, 'upload_speed'):
|
||||||
|
if result.upload_speed > 500:
|
||||||
|
score += 0.3
|
||||||
|
elif result.upload_speed > 200:
|
||||||
|
score += 0.2
|
||||||
|
elif result.upload_speed > 100:
|
||||||
|
score += 0.1
|
||||||
|
|
||||||
|
# Available slots bonus
|
||||||
|
if hasattr(result, 'free_upload_slots') and result.free_upload_slots > 0:
|
||||||
|
score += 0.2
|
||||||
|
|
||||||
|
# Low queue bonus
|
||||||
|
if hasattr(result, 'queue_length'):
|
||||||
|
if result.queue_length < 5:
|
||||||
|
score += 0.1
|
||||||
|
elif result.queue_length > 20:
|
||||||
|
score -= 0.1
|
||||||
|
|
||||||
|
return max(0.0, min(score, 1.0))
|
||||||
|
|
||||||
|
def _calculate_freshness_score(self, result):
|
||||||
|
"""Calculate file freshness/naming quality score (0.0 to 1.0)"""
|
||||||
|
score = 0.0
|
||||||
|
|
||||||
|
filename = ""
|
||||||
|
if hasattr(result, 'album_title'): # AlbumResult
|
||||||
|
filename = result.album_title
|
||||||
|
elif hasattr(result, 'filename'): # TrackResult
|
||||||
|
filename = result.filename
|
||||||
|
|
||||||
|
if filename:
|
||||||
|
# Proper naming patterns bonus
|
||||||
|
if any(pattern in filename.lower() for pattern in [' - ', '_', ' / ', ' & ']):
|
||||||
|
score += 0.2
|
||||||
|
|
||||||
|
# Standard format bonus
|
||||||
|
if any(ext in filename.lower() for ext in ['.flac', '.mp3', '.ogg', '.aac']):
|
||||||
|
score += 0.1
|
||||||
|
|
||||||
|
# Avoid weird characters penalty
|
||||||
|
if any(char in filename for char in ['@', '#', '$', '%', '!', '?']):
|
||||||
|
score -= 0.1
|
||||||
|
|
||||||
|
# Length bonus (not too short, not too long)
|
||||||
|
if 10 <= len(filename) <= 100:
|
||||||
|
score += 0.1
|
||||||
|
|
||||||
|
return max(0.0, min(score, 1.0))
|
||||||
|
|
||||||
def _sort_by_quality(self, result):
|
def _sort_by_quality(self, result):
|
||||||
"""Sort by quality score (higher is better)"""
|
"""Sort by quality score (higher is better)"""
|
||||||
if hasattr(result, 'quality_score'):
|
if hasattr(result, 'quality_score'):
|
||||||
|
|
@ -4441,9 +4613,10 @@ class DownloadsPage(QWidget):
|
||||||
self.is_loading_more = False
|
self.is_loading_more = False
|
||||||
self.currently_expanded_item = None # Reset expanded state
|
self.currently_expanded_item = None # Reset expanded state
|
||||||
|
|
||||||
# Reset filter to "all" and sort to "quality", hide filter controls
|
# Reset filter to "all" and sort to "relevance", hide filter controls
|
||||||
self.current_filter = "all"
|
self.current_filter = "all"
|
||||||
self.current_sort = "quality"
|
self.current_sort = "relevance"
|
||||||
|
self.current_search_query = query # Store search query for relevance calculation
|
||||||
if hasattr(self, 'filter_buttons'):
|
if hasattr(self, 'filter_buttons'):
|
||||||
for btn_key, btn in self.filter_buttons.items():
|
for btn_key, btn in self.filter_buttons.items():
|
||||||
self.update_filter_button_style(btn, btn_key == "all")
|
self.update_filter_button_style(btn, btn_key == "all")
|
||||||
|
|
@ -4452,7 +4625,7 @@ class DownloadsPage(QWidget):
|
||||||
self.update_filter_button_style(btn, btn_key == "all")
|
self.update_filter_button_style(btn, btn_key == "all")
|
||||||
if hasattr(self, 'sort_buttons'):
|
if hasattr(self, 'sort_buttons'):
|
||||||
for btn_key, btn in self.sort_buttons.items():
|
for btn_key, btn in self.sort_buttons.items():
|
||||||
self.update_filter_button_style(btn, btn_key == "quality")
|
self.update_filter_button_style(btn, btn_key == "relevance")
|
||||||
self.filter_container.setVisible(False)
|
self.filter_container.setVisible(False)
|
||||||
|
|
||||||
# Enhanced searching state with animation
|
# Enhanced searching state with animation
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue