pull/15/head
Broque Thomas 9 months ago
parent 5142b5153e
commit bb6be656e0

@ -504,16 +504,93 @@ def start_sync():
@app.route('/api/search', methods=['POST'])
def search_music():
# Placeholder: simulates a music search
"""Real search using soulseek_client"""
data = request.get_json()
query = data.get('query', '')
print(f"Simulating search for: {query}")
# In a real implementation, you would call soulseek_client.search()
mock_results = [
{"title": "Bohemian Rhapsody", "artist": "Queen", "album": "A Night at the Opera", "type": "track", "quality": "FLAC", "username": "user1", "filename": "Queen - Bohemian Rhapsody.flac", "file_size": 35000000},
{"title": "A Night at the Opera", "artist": "Queen", "type": "album", "track_count": 12, "size_mb": 350, "username": "user2"}
]
return jsonify({"results": mock_results})
query = data.get('query')
if not query:
return jsonify({"error": "No search query provided."}), 400
print(f"Web UI Search for: '{query}'")
try:
tracks, albums = asyncio.run(soulseek_client.search(query))
# Convert to dictionaries for JSON response
processed_albums = []
for album in albums:
album_dict = album.__dict__.copy()
album_dict["tracks"] = [track.__dict__ for track in album.tracks]
album_dict["result_type"] = "album"
processed_albums.append(album_dict)
processed_tracks = []
for track in tracks:
track_dict = track.__dict__.copy()
track_dict["result_type"] = "track"
processed_tracks.append(track_dict)
# Sort by quality score
all_results = sorted(processed_albums + processed_tracks, key=lambda x: x.get('quality_score', 0), reverse=True)
return jsonify({"results": all_results})
except Exception as e:
print(f"Search error: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/download', methods=['POST'])
def start_download():
"""Simple download route"""
data = request.get_json()
if not data:
return jsonify({"error": "No download data provided."}), 400
try:
result_type = data.get('result_type', 'track')
if result_type == 'album':
tracks = data.get('tracks', [])
if not tracks:
return jsonify({"error": "No tracks found in album."}), 400
started_downloads = 0
for track_data in tracks:
try:
download_id = asyncio.run(soulseek_client.download(
track_data.get('username'),
track_data.get('filename'),
track_data.get('size', 0)
))
if download_id:
started_downloads += 1
except Exception as e:
print(f"Failed to start track download: {e}")
continue
return jsonify({
"success": True,
"message": f"Started {started_downloads} downloads from album"
})
else:
# Single track download
username = data.get('username')
filename = data.get('filename')
file_size = data.get('size', 0)
if not username or not filename:
return jsonify({"error": "Missing username or filename."}), 400
download_id = asyncio.run(soulseek_client.download(username, filename, file_size))
if download_id:
return jsonify({"success": True, "message": "Download started"})
else:
return jsonify({"error": "Failed to start download"}), 500
except Exception as e:
print(f"Download error: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/artists')
def get_artists():

@ -924,6 +924,154 @@ async function loadSyncData() {
async function loadDownloadsData() {
// Downloads page loads search results dynamically
console.log('Downloads page loaded');
// Connect downloads search button
const searchButton = document.getElementById('downloads-search-btn');
const searchInput = document.getElementById('downloads-search-input');
if (searchButton && searchInput) {
searchButton.addEventListener('click', performDownloadsSearch);
searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') performDownloadsSearch();
});
}
}
async function performDownloadsSearch() {
const query = document.getElementById('downloads-search-input').value.trim();
if (!query) {
showToast('Please enter a search term', 'error');
return;
}
try {
showLoadingOverlay('Searching...');
const response = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
});
const data = await response.json();
if (data.error) {
showToast(`Search error: ${data.error}`, 'error');
return;
}
const results = data.results || [];
displayDownloadsResults(results);
if (results.length === 0) {
showToast('No results found', 'error');
} else {
showToast(`Found ${results.length} results`, 'success');
}
} catch (error) {
console.error('Search failed:', error);
showToast('Search failed', 'error');
} finally {
hideLoadingOverlay();
}
}
function displayDownloadsResults(results) {
const resultsArea = document.getElementById('search-results-area');
if (!resultsArea) return;
if (!results.length) {
resultsArea.innerHTML = '<div class="search-results-placeholder"><p>No search results found.</p></div>';
return;
}
let html = '';
results.forEach((result, index) => {
const isAlbum = result.result_type === 'album';
if (isAlbum) {
const trackCount = result.tracks ? result.tracks.length : 0;
html += `
<div class="search-result-item album-result">
<div class="result-info">
<strong>🎵 ${escapeHtml(result.album_title || result.title || 'Unknown Album')}</strong><br>
<span>by ${escapeHtml(result.artist || 'Unknown Artist')}</span><br>
<small>${trackCount} tracks ${escapeHtml(result.quality || 'Mixed')}</small>
</div>
<button onclick="downloadAlbum(${index})" class="download-btn"> Download Album</button>
</div>
`;
} else {
const sizeText = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : 'Unknown size';
html += `
<div class="search-result-item track-result">
<div class="result-info">
<strong>${escapeHtml(result.title || 'Unknown Title')}</strong><br>
<span>by ${escapeHtml(result.artist || 'Unknown Artist')}</span><br>
<small>${sizeText} ${escapeHtml(result.quality || 'Unknown')} ${result.bitrate ? `${result.bitrate}kbps` : ''}</small>
</div>
<button onclick="downloadTrack(${index})" class="download-btn"> Download</button>
</div>
`;
}
});
resultsArea.innerHTML = html;
// Store results globally for download functions
window.currentSearchResults = results;
}
async function downloadTrack(index) {
const results = window.currentSearchResults;
if (!results || !results[index]) return;
const track = results[index];
try {
const response = await fetch('/api/download', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(track)
});
const data = await response.json();
if (data.success) {
showToast(`Download started: ${track.title}`, 'success');
} else {
showToast(`Download failed: ${data.error}`, 'error');
}
} catch (error) {
console.error('Download error:', error);
showToast('Failed to start download', 'error');
}
}
async function downloadAlbum(index) {
const results = window.currentSearchResults;
if (!results || !results[index]) return;
const album = results[index];
try {
const response = await fetch('/api/download', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(album)
});
const data = await response.json();
if (data.success) {
showToast(data.message, 'success');
} else {
showToast(`Album download failed: ${data.error}`, 'error');
}
} catch (error) {
console.error('Album download error:', error);
showToast('Failed to start album download', 'error');
}
}
async function loadArtistsData() {
@ -1275,4 +1423,6 @@ window.authenticateTidal = authenticateTidal;
window.browsePath = browsePath;
window.selectResult = selectResult;
window.startStream = startStream;
window.startDownload = startDownload;
window.startDownload = startDownload;
window.downloadTrack = downloadTrack;
window.downloadAlbum = downloadAlbum;

@ -2006,4 +2006,36 @@ body {
.version-modal-overlay:not(.hidden) .version-modal {
animation: modalFadeIn 0.3s ease-out;
}
/* Simple Downloads Results Styling */
.search-result-item {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.search-result-item .result-info {
flex: 1;
}
.search-result-item .download-btn {
background: rgba(29, 185, 84, 0.1);
color: #1ed760;
border: 1px solid rgba(29, 185, 84, 0.3);
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
}
.search-result-item .download-btn:hover {
background: rgba(29, 185, 84, 0.2);
border-color: rgba(29, 185, 84, 0.5);
}
Loading…
Cancel
Save