diff --git a/core/soulseek_client.py b/core/soulseek_client.py
index 36f1f750..d275fff1 100644
--- a/core/soulseek_client.py
+++ b/core/soulseek_client.py
@@ -1187,6 +1187,33 @@ class SoulseekClient:
))
return results
+ async def cancel_all_downloads(self) -> bool:
+ """Cancel and remove ALL downloads (active + completed) from slskd.
+
+ Returns:
+ bool: True if successful, False otherwise
+ """
+ if not self.base_url:
+ logger.error("Soulseek client not configured")
+ return False
+
+ try:
+ endpoint = 'transfers/downloads/all'
+ logger.debug(f"Cancelling all downloads with endpoint: {endpoint}")
+ response = await self._make_request('DELETE', endpoint)
+ success = response is not None
+
+ if success:
+ logger.info("Successfully cancelled all downloads from slskd")
+ else:
+ logger.error("Failed to cancel all downloads from slskd")
+
+ return success
+
+ except Exception as e:
+ logger.error(f"Error cancelling all downloads: {e}")
+ return False
+
async def clear_all_completed_downloads(self) -> bool:
"""Clear all completed/finished downloads from slskd backend
diff --git a/web_server.py b/web_server.py
index 37ce5fd5..b77a2bbb 100644
--- a/web_server.py
+++ b/web_server.py
@@ -4981,6 +4981,28 @@ def cancel_download():
print(f"Error cancelling download: {e}")
return jsonify({"success": False, "error": str(e)}), 500
+@app.route('/api/downloads/cancel-all', methods=['POST'])
+def cancel_all_downloads():
+ """
+ Cancel all active downloads from slskd, then clear completed ones.
+ """
+ try:
+ # First cancel all active downloads
+ cancel_success = run_async(soulseek_client.cancel_all_downloads())
+ if not cancel_success:
+ return jsonify({"success": False, "error": "Failed to cancel active downloads."}), 500
+
+ # Then clear the now-cancelled/completed downloads
+ clear_success = run_async(soulseek_client.clear_all_completed_downloads())
+
+ # Sweep empty directories
+ _sweep_empty_download_directories()
+
+ return jsonify({"success": True, "message": "All downloads cancelled and cleared."})
+ except Exception as e:
+ print(f"Error cancelling all downloads: {e}")
+ return jsonify({"success": False, "error": str(e)}), 500
+
@app.route('/api/downloads/clear-finished', methods=['POST'])
def clear_finished_downloads():
"""
diff --git a/webui/index.html b/webui/index.html
index 4e62dd3c..44fef070 100644
--- a/webui/index.html
+++ b/webui/index.html
@@ -1718,6 +1718,7 @@
+
diff --git a/webui/static/script.js b/webui/static/script.js
index 21b0675b..16b8def8 100644
--- a/webui/static/script.js
+++ b/webui/static/script.js
@@ -9887,10 +9887,14 @@ async function loadDownloadsData() {
// Event listeners are already set up in initializeSearch() - don't duplicate them
const clearButton = document.querySelector('.controls-panel__clear-btn');
+ const cancelAllButton = document.querySelector('.controls-panel__cancel-all-btn');
if (clearButton) {
clearButton.addEventListener('click', clearFinishedDownloads);
}
+ if (cancelAllButton) {
+ cancelAllButton.addEventListener('click', cancelAllDownloads);
+ }
// Start sophisticated polling system (1-second interval like GUI)
startDownloadPolling();
@@ -10157,6 +10161,28 @@ async function clearFinishedDownloads() {
}
}
+async function cancelAllDownloads() {
+ if (!confirm('Cancel ALL active downloads and clear the transfer list? This cannot be undone.')) {
+ return;
+ }
+
+ try {
+ const response = await fetch('/api/downloads/cancel-all', {
+ method: 'POST'
+ });
+ const result = await response.json();
+
+ if (result.success) {
+ showToast('All downloads cancelled and cleared', 'success');
+ } else {
+ showToast(`Failed to cancel: ${result.error}`, 'error');
+ }
+ } catch (error) {
+ console.error('Error cancelling all downloads:', error);
+ showToast('Error cancelling downloads', 'error');
+ }
+}
+
// REPLACE the old performDownloadsSearch function with this new one.
async function performDownloadsSearch() {
const query = document.getElementById('downloads-search-input').value.trim();
diff --git a/webui/static/style.css b/webui/static/style.css
index fc732964..78c02561 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -2641,6 +2641,23 @@ body {
background: linear-gradient(to bottom, rgba(220, 53, 69, 0.5), rgba(220, 53, 69, 0.35));
}
+.controls-panel__cancel-all-btn {
+ width: 100%;
+ height: 28px;
+ margin-top: 4px;
+ background: linear-gradient(to bottom, rgba(255, 152, 0, 0.4), rgba(255, 152, 0, 0.25));
+ border: 1px solid rgba(255, 152, 0, 0.8);
+ color: #ff9800;
+ border-radius: 14px;
+ font-size: 10px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.2s ease;
+}
+
+.controls-panel__cancel-all-btn:hover {
+ background: linear-gradient(to bottom, rgba(255, 152, 0, 0.5), rgba(255, 152, 0, 0.35));
+}
/* Download Manager (Tabs & Queue) */
.download-manager {