working
This commit is contained in:
parent
3eb7dc8fda
commit
05718ac92d
7 changed files with 2421 additions and 310 deletions
|
|
@ -1 +1 @@
|
|||
{"access_token": "BQBLSLPESDPqAZT7HXU_WwksU6Wjz1fHsiSr43EiG7xD2dHnKJZ4yEaXlxj7cN8BrneCT4aQrfSxEUJMQNPh8kpS2WHaMWTBnmkyk3FVDjhLXOf4vOF893-4xB0yYh2aDJTGnS5dZlqS2Lkvq9CDzTDsIVel8iNXaTnyne90Nb71OtNNjz1_QcpwVEfESFuYBuGI1plfrRguJ_QDl9_Fuejxz79q1jqmONUPYyBBL_aHPpyLiLn7nC0w6gbhqsZi", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752289745, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
|
||||
{"access_token": "BQB9Vr_28li6MRQ88yjZzlwyGHUqaOBYlY7bcNrUOgmO7KINcVXVS149FpyDcvDBaENi3TeOtluApd0z14T923HrirGeIdXu9QK_XYDw91JhdLRhCgTXwZGK33kAh7tF3FSqdpPFNmwGyjZx1NhBxG3MhnDlYQr2EzXP0zDA2rrzbFwwPTlT5FYIXowtgzduQJThiLOLOcFvPnACdNkQxv4uJzJh6GWr999WiUzsS8t-Pwn0YirnI3iEhiMvcB3a", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752293296, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
|
||||
99
CLAUDE.md
99
CLAUDE.md
|
|
@ -38,59 +38,60 @@ The application will use a central `config.json` file to store:
|
|||
- ✅ Configuration management system
|
||||
- ✅ Download progress tracking for both singles and albums
|
||||
- ✅ Enhanced filename matching system preventing false positives
|
||||
- ✅ Compact download queue UI with proper space utilization
|
||||
- ✅ Complete download queue management system with functional buttons
|
||||
- ✅ Compact, practical download item UI design
|
||||
- ✅ Album track button state management and cancellation handling
|
||||
- ✅ Clear completed downloads functionality with backend integration
|
||||
|
||||
**Recently Completed Work**:
|
||||
|
||||
### ✅ Download Manager Complete Redesign (COMPLETED)
|
||||
- **COMPLETED**: Fully functional Cancel and Open buttons in download queue
|
||||
- **COMPLETED**: Proper slskd API integration with correct endpoint formats
|
||||
- **COMPLETED**: Enhanced album track button state management with cancellation support
|
||||
- **COMPLETED**: Clear All Completed Downloads functionality using slskd backend API
|
||||
- **COMPLETED**: Compact download item UI redesign for optimal space utilization
|
||||
|
||||
### ✅ Download Queue Button Functionality (RESOLVED)
|
||||
- **FIXED**: Cancel button now properly cancels downloads using correct slskd API format
|
||||
- **FIXED**: Open button successfully opens download folders with fallback logic
|
||||
- **FIXED**: Enhanced debugging and error handling for button operations
|
||||
- **FIXED**: Album track buttons properly reset after individual track cancellation
|
||||
|
||||
### ✅ UI/UX Improvements (COMPLETED)
|
||||
- **COMPLETED**: Redesigned CompactDownloadItem with efficient 45px height
|
||||
- **COMPLETED**: Conditional layout system (active: filename/uploader/progress/cancel, finished: filename/uploader/open)
|
||||
- **COMPLETED**: Smart text ellipsis handling preventing horizontal overflow
|
||||
- **COMPLETED**: Optimized space allocation fitting perfectly in download queue container
|
||||
- **COMPLETED**: Clean, functional button design with immediate accessibility
|
||||
|
||||
**Active Work**:
|
||||
- 🔧 Download Manager button functionality (Cancel/Open buttons)
|
||||
- ⏳ Additional UI polish and user experience improvements
|
||||
- ⏳ Matching engine development for cross-service track matching
|
||||
|
||||
**Recently Resolved Issues**:
|
||||
|
||||
### ✅ Download Tracking System (RESOLVED)
|
||||
- **FIXED**: Download progress tracking now works correctly for both singles and albums
|
||||
- **FIXED**: Album tracks no longer show same name in active downloads - enhanced filename matching prevents false matches
|
||||
- **FIXED**: Download ID management and API integration properly handles real UUIDs
|
||||
- **FIXED**: UI state synchronization for completed downloads with proper queue transitions
|
||||
|
||||
**Current Issues Requiring Attention**:
|
||||
|
||||
### Download Manager Button Issues
|
||||
**Priority**: High - Download queue buttons non-functional
|
||||
|
||||
**Problem Summary**: The Cancel and Open buttons in download queue interface are not working when clicked.
|
||||
|
||||
**Specific Issues**:
|
||||
1. **Cancel Button Not Working**:
|
||||
- Location: CompactDownloadItem cancel button in active download queue
|
||||
- Problem: Button clicks not triggering download cancellation
|
||||
- Likely causes: Signal connection issues or incorrect download ID usage for API calls
|
||||
- Expected behavior: Should cancel active downloads and remove from queue
|
||||
|
||||
2. **Open Button Not Working**:
|
||||
- Location: CompactDownloadItem open button in finished downloads section
|
||||
- Problem: Button clicks not opening download folder location
|
||||
- Likely causes: Missing signal connections, incorrect file paths, or silent error handling
|
||||
- Expected behavior: Should open file explorer to show downloaded files
|
||||
|
||||
**Investigation Needed**:
|
||||
- Verify button signal connections in CompactDownloadItem class
|
||||
- Check if download IDs are properly passed to cancellation methods
|
||||
- Validate file paths for completed downloads
|
||||
- Add user feedback for button operation failures
|
||||
|
||||
### Future UI Improvements
|
||||
**Priority**: Low - Additional enhancements for later consideration
|
||||
|
||||
**Potential Changes**:
|
||||
1. Remove "Pause All" button (not needed)
|
||||
2. Fix "Clear Completed" functionality (currently doesn't work)
|
||||
3. Additional download queue management features
|
||||
**Current System Status**: All major download management functionality is working correctly.
|
||||
|
||||
## Key Components Status
|
||||
|
||||
1. **Configuration Management**: ✅ Implemented - Secure handling of API keys and service credentials
|
||||
2. **Spotify Integration**: ✅ Implemented - Playlist retrieval and metadata extraction
|
||||
3. **Plex Integration**: ✅ Implemented - Media server synchronization and metadata updates
|
||||
4. **Soulseek Integration**: ✅ Mostly Complete - Music discovery and download tracking working, minor button issues remain
|
||||
5. **Matching Engine**: ⏳ Planned - Robust algorithms for matching tracks across services
|
||||
6. **User Interface**: ✅ Mostly Complete - Spotify-inspired design with modern, animated elements
|
||||
1. **Configuration Management**: ✅ Complete - Secure handling of API keys and service credentials
|
||||
2. **Spotify Integration**: ✅ Complete - Playlist retrieval and metadata extraction
|
||||
3. **Plex Integration**: ✅ Complete - Media server synchronization and metadata updates
|
||||
4. **Soulseek Integration**: ✅ Complete - Full music discovery, download management, and queue functionality
|
||||
5. **Download Management**: ✅ Complete - Comprehensive download queue with cancel/open functionality
|
||||
6. **User Interface**: ✅ Complete - Spotify-inspired design with practical, efficient download management
|
||||
7. **Matching Engine**: ⏳ In Development - Robust algorithms for matching tracks across services
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### Download Management System
|
||||
- **CompactDownloadItem**: Efficient 45px height design with conditional layouts
|
||||
- **slskd API Integration**: Proper DELETE endpoints with username/download_id parameters
|
||||
- **Album Track Management**: State synchronization between album buttons and download queue
|
||||
- **Clear Completed**: Backend integration for removing finished downloads from slskd
|
||||
- **Progress Tracking**: Real-time updates with percentage display and status monitoring
|
||||
|
||||
### UI Design Philosophy
|
||||
- **Function over Form**: Practical, user-focused design prioritizing usability
|
||||
- **Container Responsive**: Optimized for small download queue areas without horizontal scrolling
|
||||
- **Efficient Space Usage**: Conditional layouts maximize available space
|
||||
- **Immediate Accessibility**: Buttons positioned for quick access without scrolling
|
||||
Binary file not shown.
2170
logs/app.log
2170
logs/app.log
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
|
|
@ -2098,157 +2098,162 @@ class CompactDownloadItem(QFrame):
|
|||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
self.setFixedHeight(45) # Compact height for better space utilization
|
||||
self.setFixedHeight(45) # Compact height for efficient space usage
|
||||
self.setStyleSheet("""
|
||||
CompactDownloadItem {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(45, 45, 45, 0.8),
|
||||
stop:1 rgba(35, 35, 35, 0.9));
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(64, 64, 64, 0.3);
|
||||
background: rgba(45, 45, 45, 0.95);
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(60, 60, 60, 0.6);
|
||||
margin: 2px 1px;
|
||||
}
|
||||
CompactDownloadItem:hover {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(55, 55, 55, 0.9),
|
||||
stop:1 rgba(45, 45, 45, 0.95));
|
||||
background: rgba(55, 55, 55, 1.0);
|
||||
border: 1px solid rgba(29, 185, 84, 0.5);
|
||||
}
|
||||
""")
|
||||
|
||||
# Main horizontal layout
|
||||
# Main horizontal layout - compact and practical
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 6, 10, 6) # Compact margins
|
||||
layout.setSpacing(8) # Tight spacing
|
||||
layout.setContentsMargins(8, 6, 8, 6) # Compact margins
|
||||
layout.setSpacing(8) # Tight but adequate spacing
|
||||
|
||||
# Left section: Filename + uploader (flexible)
|
||||
left_section = QVBoxLayout()
|
||||
left_section.setSpacing(1) # Very tight vertical spacing
|
||||
left_section.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Extract just filename with extension from file_path
|
||||
# Section 1: Filename (flexible width with ellipsis)
|
||||
filename_with_ext = self.get_display_filename()
|
||||
|
||||
# Filename with extension (main info)
|
||||
self.filename_label = QLabel(filename_with_ext)
|
||||
self.filename_label.setFont(QFont("Segoe UI", 10, QFont.Weight.Bold))
|
||||
self.filename_label.setStyleSheet("color: #ffffff;")
|
||||
self.filename_label.setFont(QFont("Segoe UI", 11, QFont.Weight.Medium))
|
||||
self.filename_label.setStyleSheet("color: #ffffff; background: transparent;")
|
||||
self.filename_label.setWordWrap(False)
|
||||
self.filename_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.filename_label.setToolTip(filename_with_ext) # Full filename on hover
|
||||
|
||||
# Uploader info (compact)
|
||||
uploader_label = QLabel(f"from {self.artist}")
|
||||
uploader_label.setFont(QFont("Segoe UI", 8))
|
||||
uploader_label.setStyleSheet("color: #b3b3b3;")
|
||||
uploader_label.setWordWrap(False)
|
||||
uploader_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
# Section 2: Uploader (fixed width with ellipsis)
|
||||
uploader_width = 80 if self.queue_type == "active" else 90
|
||||
self.uploader_label = QLabel()
|
||||
self.uploader_label.setFont(QFont("Segoe UI", 9, QFont.Weight.Normal))
|
||||
self.uploader_label.setStyleSheet("color: #b8b8b8; background: transparent;")
|
||||
self.uploader_label.setFixedWidth(uploader_width)
|
||||
self.uploader_label.setWordWrap(False)
|
||||
self.uploader_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.uploader_label.setToolTip(f"Uploader: {self.artist}")
|
||||
# Set ellipsis text for uploader
|
||||
self._set_ellipsis_text(self.uploader_label, self.artist, uploader_width)
|
||||
|
||||
left_section.addWidget(self.filename_label)
|
||||
left_section.addWidget(uploader_label)
|
||||
|
||||
# Middle section: Progress (fixed width)
|
||||
progress_widget = QWidget()
|
||||
progress_widget.setFixedWidth(80) # Narrower than full version
|
||||
progress_layout = QVBoxLayout(progress_widget)
|
||||
progress_layout.setSpacing(2)
|
||||
progress_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Progress bar (smaller)
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setFixedHeight(6) # Thinner progress bar
|
||||
self.progress_bar.setValue(self.progress)
|
||||
self.progress_bar.setStyleSheet("""
|
||||
QProgressBar {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
background: #404040;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background: #1db954;
|
||||
border-radius: 3px;
|
||||
}
|
||||
""")
|
||||
|
||||
# Status text (compact)
|
||||
status_text = self.get_status_text()
|
||||
self.status_label = QLabel(status_text)
|
||||
self.status_label.setFont(QFont("Segoe UI", 8))
|
||||
self.status_label.setStyleSheet("color: #b3b3b3;")
|
||||
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
progress_layout.addWidget(self.progress_bar)
|
||||
progress_layout.addWidget(self.status_label)
|
||||
|
||||
# Right section: Action button(s) (fixed width)
|
||||
if self.queue_type == "finished":
|
||||
# For finished downloads, create a container with two buttons
|
||||
button_container = QWidget()
|
||||
button_container.setFixedWidth(150) # Wider to fit two buttons
|
||||
button_layout = QHBoxLayout(button_container)
|
||||
button_layout.setContentsMargins(0, 0, 0, 0)
|
||||
button_layout.setSpacing(4)
|
||||
# Conditional layout based on queue type
|
||||
if self.queue_type == "active":
|
||||
# Section 3: Progress (90px width for active downloads only)
|
||||
progress_widget = QWidget()
|
||||
progress_widget.setFixedWidth(90)
|
||||
progress_layout = QVBoxLayout(progress_widget)
|
||||
progress_layout.setContentsMargins(0, 0, 0, 0)
|
||||
progress_layout.setSpacing(1)
|
||||
|
||||
# Open button
|
||||
self.open_btn = QPushButton("📂")
|
||||
self.open_btn.setFixedSize(32, 32)
|
||||
self.open_btn.setToolTip("Open download folder")
|
||||
# Compact progress bar
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setFixedHeight(6)
|
||||
self.progress_bar.setValue(self.progress)
|
||||
self.progress_bar.setStyleSheet("""
|
||||
QProgressBar {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
background: rgba(60, 60, 60, 0.8);
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background: rgba(29, 185, 84, 1.0);
|
||||
border-radius: 3px;
|
||||
}
|
||||
""")
|
||||
|
||||
# Progress percentage
|
||||
self.progress_label = QLabel(f"{self.progress}%")
|
||||
self.progress_label.setFont(QFont("Segoe UI", 8))
|
||||
self.progress_label.setStyleSheet("color: #c0c0c0;")
|
||||
self.progress_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
progress_layout.addWidget(self.progress_bar)
|
||||
progress_layout.addWidget(self.progress_label)
|
||||
|
||||
# Section 4: Cancel button
|
||||
self.cancel_btn = QPushButton("Cancel")
|
||||
self.cancel_btn.setFixedSize(60, 35)
|
||||
self.cancel_btn.clicked.connect(self.cancel_download)
|
||||
self.cancel_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: rgba(220, 53, 69, 0.9);
|
||||
color: white;
|
||||
border: 1px solid rgba(220, 53, 69, 0.6);
|
||||
border-radius: 4px;
|
||||
font-size: 9px;
|
||||
font-weight: 500;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: rgba(240, 73, 89, 1.0);
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background: rgba(200, 43, 58, 1.0);
|
||||
}
|
||||
""")
|
||||
|
||||
# Add to layout: filename + uploader + progress + cancel
|
||||
layout.addWidget(self.filename_label, 1)
|
||||
layout.addWidget(self.uploader_label)
|
||||
layout.addWidget(progress_widget)
|
||||
layout.addWidget(self.cancel_btn)
|
||||
|
||||
else:
|
||||
# Finished downloads: filename + uploader + open (no progress section)
|
||||
self.progress_bar = None
|
||||
self.progress_label = None
|
||||
|
||||
# Section 3: Open button (only button for finished downloads)
|
||||
self.open_btn = QPushButton("Open")
|
||||
self.open_btn.setFixedSize(60, 35)
|
||||
self.open_btn.clicked.connect(self.open_download_location)
|
||||
self.open_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(40, 167, 69, 0.8),
|
||||
stop:1 rgba(40, 167, 69, 1.0));
|
||||
background: rgba(40, 167, 69, 0.9);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
border: 1px solid rgba(29, 185, 84, 0.6);
|
||||
border-radius: 4px;
|
||||
font-size: 9px;
|
||||
font-weight: 500;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(20, 147, 49, 0.9),
|
||||
stop:1 rgba(20, 147, 49, 1.0));
|
||||
background: rgba(50, 187, 79, 1.0);
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background: rgba(32, 140, 58, 1.0);
|
||||
}
|
||||
""")
|
||||
|
||||
# Remove button
|
||||
self.remove_btn = QPushButton("🗑️")
|
||||
self.remove_btn.setFixedSize(32, 32)
|
||||
self.remove_btn.setToolTip("Remove from downloads")
|
||||
self.remove_btn.clicked.connect(self.remove_download)
|
||||
self.remove_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(220, 53, 69, 0.8),
|
||||
stop:1 rgba(220, 53, 69, 1.0));
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(200, 33, 49, 0.9),
|
||||
stop:1 rgba(200, 33, 49, 1.0));
|
||||
}
|
||||
""")
|
||||
|
||||
button_layout.addWidget(self.open_btn)
|
||||
button_layout.addWidget(self.remove_btn)
|
||||
|
||||
action_widget = button_container
|
||||
else:
|
||||
# For active downloads, single action button
|
||||
self.action_btn = QPushButton()
|
||||
self.action_btn.setFixedSize(70, 32) # Smaller button
|
||||
self.setup_action_button()
|
||||
action_widget = self.action_btn
|
||||
# Add to layout: filename + uploader + open (no progress)
|
||||
layout.addWidget(self.filename_label, 1)
|
||||
layout.addWidget(self.uploader_label)
|
||||
layout.addWidget(self.open_btn)
|
||||
|
||||
def _set_ellipsis_text(self, label, text, max_width):
|
||||
"""Set text with ellipsis if it's too long for the given width"""
|
||||
font_metrics = label.fontMetrics()
|
||||
# Reserve some padding space (8px total)
|
||||
available_width = max_width - 8
|
||||
|
||||
# Add everything to main layout
|
||||
layout.addLayout(left_section, 1) # Flexible
|
||||
layout.addWidget(progress_widget) # Fixed width
|
||||
layout.addWidget(action_widget) # Fixed width
|
||||
if font_metrics.horizontalAdvance(text) <= available_width:
|
||||
label.setText(text)
|
||||
else:
|
||||
# Truncate with ellipsis
|
||||
ellipsis_width = font_metrics.horizontalAdvance("...")
|
||||
available_for_text = available_width - ellipsis_width
|
||||
|
||||
# Binary search for the right length
|
||||
left, right = 0, len(text)
|
||||
while left < right:
|
||||
mid = (left + right + 1) // 2
|
||||
if font_metrics.horizontalAdvance(text[:mid]) <= available_for_text:
|
||||
left = mid
|
||||
else:
|
||||
right = mid - 1
|
||||
|
||||
truncated_text = text[:left] + "..."
|
||||
label.setText(truncated_text)
|
||||
|
||||
def get_display_filename(self):
|
||||
"""Extract just the filename with extension for display"""
|
||||
|
|
@ -2274,151 +2279,65 @@ class CompactDownloadItem(QFrame):
|
|||
}
|
||||
return status_mapping.get(self.status.lower(), self.status.title())
|
||||
|
||||
def setup_action_button(self):
|
||||
"""Setup the action button based on queue type and status"""
|
||||
# Clear any existing connections
|
||||
try:
|
||||
self.action_btn.clicked.disconnect()
|
||||
except:
|
||||
pass
|
||||
|
||||
if self.queue_type == "active":
|
||||
# Active downloads: Cancel or Retry buttons
|
||||
if self.status.lower() in ["downloading", "queued"]:
|
||||
self.action_btn.setText("Cancel")
|
||||
self.action_btn.clicked.connect(self.cancel_download)
|
||||
print(f"[DEBUG] Connected cancel button for: {self.title}")
|
||||
self.action_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(220, 53, 69, 0.8),
|
||||
stop:1 rgba(220, 53, 69, 1.0));
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(200, 33, 49, 0.9),
|
||||
stop:1 rgba(200, 33, 49, 1.0));
|
||||
}
|
||||
""")
|
||||
elif self.status.lower() == "failed":
|
||||
self.action_btn.setText("Retry")
|
||||
self.action_btn.clicked.connect(self.retry_download)
|
||||
self.action_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(255, 193, 7, 0.8),
|
||||
stop:1 rgba(255, 193, 7, 1.0));
|
||||
color: #000;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(235, 173, 0, 0.9),
|
||||
stop:1 rgba(235, 173, 0, 1.0));
|
||||
}
|
||||
""")
|
||||
else:
|
||||
# Completed but still in active queue - this shouldn't happen normally
|
||||
self.action_btn.setText("Done")
|
||||
self.action_btn.setEnabled(False)
|
||||
self.action_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: rgba(100, 100, 100, 0.3);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
""")
|
||||
else:
|
||||
# Finished downloads: Always show Open button
|
||||
self.action_btn.setText("📂 Open")
|
||||
self.action_btn.clicked.connect(self.open_download_location)
|
||||
print(f"[DEBUG] Connected open button for: {self.title} (path: {self.file_path})")
|
||||
self.action_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(40, 167, 69, 0.8),
|
||||
stop:1 rgba(40, 167, 69, 1.0));
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(20, 147, 49, 0.9),
|
||||
stop:1 rgba(20, 147, 49, 1.0));
|
||||
}
|
||||
""")
|
||||
|
||||
def update_status(self, status: str, progress: int = None, download_speed: int = None, file_path: str = None):
|
||||
"""Update the status and progress of the download item"""
|
||||
self.status = status
|
||||
if progress is not None:
|
||||
self.progress = progress
|
||||
self.progress_bar.setValue(progress)
|
||||
if download_speed is not None:
|
||||
self.download_speed = download_speed
|
||||
if file_path:
|
||||
self.file_path = file_path
|
||||
# Update filename display if file_path changed
|
||||
if hasattr(self, 'filename_label') and self.filename_label:
|
||||
self.filename_label.setText(self.get_display_filename())
|
||||
filename_with_ext = self.get_display_filename()
|
||||
self.filename_label.setText(filename_with_ext)
|
||||
self.filename_label.setToolTip(filename_with_ext)
|
||||
|
||||
# Update status text
|
||||
self.status_label.setText(self.get_status_text())
|
||||
|
||||
# Update action button if needed
|
||||
self.setup_action_button()
|
||||
# Update progress components for active downloads only
|
||||
if self.queue_type == "active":
|
||||
if hasattr(self, 'progress_bar') and self.progress_bar:
|
||||
self.progress_bar.setValue(self.progress)
|
||||
if hasattr(self, 'progress_label') and self.progress_label:
|
||||
self.progress_label.setText(f"{self.progress}%")
|
||||
|
||||
# Update cancel button state based on status
|
||||
if hasattr(self, 'cancel_btn') and self.cancel_btn:
|
||||
if status.lower() in ['cancelled', 'canceled', 'failed']:
|
||||
# Disable button and update text for cancelled/failed downloads
|
||||
self.cancel_btn.setText("Cancelled")
|
||||
self.cancel_btn.setEnabled(False)
|
||||
self.cancel_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: rgba(100, 100, 100, 0.5);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
border: 1px solid rgba(100, 100, 100, 0.4);
|
||||
border-radius: 4px;
|
||||
font-size: 9px;
|
||||
font-weight: 500;
|
||||
}
|
||||
""")
|
||||
elif status.lower() in ['downloading', 'queued']:
|
||||
# Re-enable button for active downloads
|
||||
self.cancel_btn.setText("Cancel")
|
||||
self.cancel_btn.setEnabled(True)
|
||||
self.cancel_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: rgba(220, 53, 69, 0.9);
|
||||
color: white;
|
||||
border: 1px solid rgba(220, 53, 69, 0.6);
|
||||
border-radius: 4px;
|
||||
font-size: 9px;
|
||||
font-weight: 500;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: rgba(240, 73, 89, 1.0);
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background: rgba(200, 43, 58, 1.0);
|
||||
}
|
||||
""")
|
||||
|
||||
def remove_download(self):
|
||||
"""Remove the download completely from transfer list"""
|
||||
print(f"[DEBUG] Remove button clicked - download_id: {self.download_id}, username: {self.username}, title: {self.title}")
|
||||
if self.soulseek_client and self.download_id:
|
||||
print(f"🗑️ Removing download: {self.download_id}")
|
||||
try:
|
||||
import asyncio
|
||||
|
||||
# Get or create event loop for async operation
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
# Run the removal with remove=true
|
||||
result = loop.run_until_complete(self.soulseek_client.cancel_download(self.download_id, self.username, remove=True))
|
||||
print(f"[DEBUG] Remove result: {result}")
|
||||
|
||||
if result:
|
||||
print(f"✅ Successfully removed download: {self.title}")
|
||||
print(f"[DEBUG] Download should be removed from slskd - waiting for next status update to reflect removal")
|
||||
# Force an immediate status update to check if it's been removed
|
||||
print(f"[DEBUG] Triggering immediate status poll to detect removal")
|
||||
# Find the downloads page and trigger status update
|
||||
downloads_page = self
|
||||
while downloads_page and not hasattr(downloads_page, 'update_download_status'):
|
||||
downloads_page = downloads_page.parent()
|
||||
if downloads_page:
|
||||
downloads_page.update_download_status()
|
||||
else:
|
||||
print(f"❌ Failed to remove download: {self.title}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to remove download: {e}")
|
||||
else:
|
||||
print(f"[DEBUG] Remove failed - soulseek_client: {self.soulseek_client}, download_id: {self.download_id}")
|
||||
|
||||
def cancel_download(self):
|
||||
"""Cancel the download using soulseek client"""
|
||||
print(f"[DEBUG] Cancel button clicked - download_id: {self.download_id}, username: {self.username}, title: {self.title}")
|
||||
|
|
@ -4082,25 +4001,46 @@ class DownloadsPage(QWidget):
|
|||
|
||||
def update_album_track_button_states(self, download_item, status):
|
||||
"""Update track download button states based on download progress"""
|
||||
print(f"[DEBUG] 🔄 Searching for track button to update: '{download_item.title}' by '{download_item.artist}' with status '{status}'")
|
||||
|
||||
# Find the track item that corresponds to this download
|
||||
for album_item in self.findChildren(AlbumResultItem):
|
||||
album_items_found = self.findChildren(AlbumResultItem)
|
||||
print(f"[DEBUG] Found {len(album_items_found)} album items to search")
|
||||
|
||||
for album_item in album_items_found:
|
||||
print(f"[DEBUG] Checking album: '{album_item.album_result.album_title}' by '{album_item.album_result.artist}' with {len(album_item.track_items)} tracks")
|
||||
|
||||
for track_item in album_item.track_items:
|
||||
track_title = track_item.track_result.title
|
||||
track_artist = track_item.track_result.artist
|
||||
|
||||
print(f"[DEBUG] Comparing track: '{track_title}' by '{track_artist}'")
|
||||
|
||||
# Match by track title and artist
|
||||
if (track_item.track_result.title == download_item.title and
|
||||
track_item.track_result.artist == download_item.artist):
|
||||
if (track_title == download_item.title and track_artist == download_item.artist):
|
||||
|
||||
print(f"[DEBUG] ✅ MATCH FOUND! Updating button state for '{track_title}' to '{status}'")
|
||||
|
||||
# Update button state based on download status
|
||||
if status == 'downloading':
|
||||
track_item.set_download_downloading_state()
|
||||
print(f"[DEBUG] Set button to downloading state (📥)")
|
||||
elif status in ['completed', 'finished']:
|
||||
track_item.set_download_completed_state()
|
||||
print(f"[DEBUG] Set button to completed state (✅)")
|
||||
elif status in ['queued', 'initializing']:
|
||||
track_item.set_download_queued_state()
|
||||
elif status in ['failed', 'cancelled']:
|
||||
print(f"[DEBUG] Set button to queued state (⏳)")
|
||||
elif status in ['failed', 'cancelled', 'canceled']:
|
||||
track_item.reset_download_state() # Allow retry
|
||||
print(f"[DEBUG] 🔓 RESET button to downloadable state (⬇️) - track can now be downloaded again!")
|
||||
else:
|
||||
print(f"[DEBUG] ⚠️ Unknown status '{status}' - no button update performed")
|
||||
|
||||
print(f"[DEBUG] Updated track button state for '{download_item.title}': {status}")
|
||||
print(f"[DEBUG] ✅ Successfully updated track button state for '{download_item.title}': {status}")
|
||||
return
|
||||
|
||||
print(f"[DEBUG] ❌ NO MATCH FOUND for track '{download_item.title}' by '{download_item.artist}' - button state not updated")
|
||||
|
||||
def start_stream(self, search_result, result_item=None):
|
||||
"""Start streaming a search result using StreamingThread or toggle if same track"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue