Initial download server added.

This commit is contained in:
Storm Dragon
2025-08-27 00:20:38 -04:00
parent 885c7ffdfc
commit 42a894da69
2 changed files with 197 additions and 0 deletions
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
import os
import sys
import socket
import subprocess
import threading
import termios
import tty
import select
import signal
import http.server
import socketserver
from threading import Timer
# Global variable to store server info for repeat announcement
server_info = {'ip': '', 'port': 0, 'httpd': None}
# Get local IP address
def get_local_ip():
try:
# Create a socket connection to determine the local IP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Doesn't even have to be reachable
s.connect(('10.255.255.255', 1))
IP = s.getsockname()[0]
s.close()
except Exception:
IP = '127.0.0.1'
return IP
# Announce server start with speech
def announce_server_start(ip, port):
formatted_ip = ""
segments = ip.split(".")
for i, segment in enumerate(segments):
# Add each digit with space between
formatted_ip += " ".join(segment)
# Add " dot " between segments (but not after the last segment)
if i < len(segments) - 1:
formatted_ip += " dot "
try:
message = f"Download server running on {formatted_ip} port {port}"
# Using spd-say with -Cw flag for better speech synthesis
subprocess.run(['spd-say', '-Cw', message])
print(f"Download server running on {ip}:{port}")
print("Press any key to repeat server information")
print("Press Ctrl+C to stop server")
except Exception as e:
print(f"Could not announce server start: {e}")
print(f"Download server running on {ip}:{port}")
# Function to repeat the server announcement
def repeat_announcement():
if server_info['ip'] and server_info['port']:
announce_server_start(server_info['ip'], server_info['port'])
# Keyboard listener function
def keyboard_listener():
"""Listen for key presses and repeat server announcement"""
if not sys.stdin.isatty():
print("Not running in a terminal, keyboard listener disabled")
return
try:
# Save original terminal settings
old_settings = termios.tcgetattr(sys.stdin)
while True:
try:
# Set terminal to raw mode to capture individual key presses
tty.setraw(sys.stdin.fileno())
# Use select to check if input is available (non-blocking)
if select.select([sys.stdin], [], [], 1) == ([sys.stdin], [], []):
# Read a single character
key = sys.stdin.read(1)
# Any key press will trigger the announcement
if key:
repeat_announcement()
except KeyboardInterrupt:
# Handle Ctrl+C gracefully
print("\nKeyboard listener interrupted")
break
except Exception as e:
print(f"Error in keyboard listener: {e}")
break
finally:
# Always restore terminal settings
try:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
except:
pass
except Exception as e:
print(f"Could not initialize keyboard listener: {e}")
# Start keyboard listener in background thread
def start_keyboard_listener():
"""Start the keyboard listener in a daemon thread"""
try:
listener_thread = threading.Thread(target=keyboard_listener, daemon=True)
listener_thread.start()
except Exception as e:
print(f"Could not start keyboard listener: {e}")
# Signal handler for graceful shutdown
def signal_handler(signum, frame):
try:
subprocess.run(['spd-say', '-Cw', 'Download server shutting down'])
except Exception:
pass
print("\nDownload server shutting down...")
if server_info['httpd']:
server_info['httpd'].shutdown()
sys.exit(0)
# Custom HTTP handler to serve files from /home/stormux
class DownloadHandler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
# Set the directory to serve files from
super().__init__(*args, directory='/home/stormux', **kwargs)
def log_message(self, format, *args):
# Log downloads with speech announcement
client_ip = self.client_address[0]
if 'GET' in format % args and not any(x in format % args for x in ['.ico', '.css', '.js']):
try:
# Only announce actual file downloads, not directory listings or favicon requests
path = args[1] if len(args) > 1 else "unknown"
if not path.endswith('/'): # It's a file, not directory
filename = os.path.basename(path)
subprocess.run(['spd-say', '-Cw', f'File {filename} downloaded by {client_ip}'],
timeout=5)
except Exception:
pass
# Still print to console
print(format % args)
if __name__ == '__main__':
print("Starting Download Server...")
# Change to the home directory to serve files from there
os.chdir('/home/stormux')
# Get local IP and set port
local_ip = get_local_ip()
port = 8000
# Store server info for repeat announcements
server_info['ip'] = local_ip
server_info['port'] = port
# Set up signal handlers for graceful shutdown
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
# Create HTTP server
with socketserver.TCPServer(("", port), DownloadHandler) as httpd:
server_info['httpd'] = httpd
# Start keyboard listener for repeat announcements
start_keyboard_listener()
# Announce server start
announce_server_start(local_ip, port)
# Start serving files
print(f"Serving files from /home/stormux at http://{local_ip}:{port}")
httpd.serve_forever()
except OSError as e:
if e.errno == 98: # Address already in use
try:
subprocess.run(['spd-say', '-Cw', f'Port {port} is already in use'])
except Exception:
pass
print(f"Error: Port {port} is already in use. Please stop any existing servers and try again.")
else:
try:
subprocess.run(['spd-say', '-Cw', 'Download server failed to start'])
except Exception:
pass
print(f"Error starting server: {e}")
sys.exit(1)
except Exception as e:
try:
subprocess.run(['spd-say', '-Cw', 'Download server error'])
except Exception:
pass
print(f"Unexpected error: {e}")
sys.exit(1)
+1
View File
@@ -1094,6 +1094,7 @@ if __name__ == "__main__":
menu.add_item("System", "Set Default Voice", "/usr/local/bin/set-voice.py")
menu.add_item("System", "Set Timezone", "GAME='Set Timezone' /home/stormux/.clirc")
menu.add_item("System", "Upload Files", "/home/stormux/.local/upload_server/uploader.py")
menu.add_item("System", "Download Files", "/home/stormux/.local/download_server.py")
menu.add_item("System", "Restart: Can Take Several Minutes", "sudo reboot")
menu.add_item("System", "Power Off: Wait 2 Minutes Before Disconnecting Power", "sudo poweroff")
# Service menu items will be added dynamically in run() method via update_service_menu_items()