modified: .gitignore
modified: app.py new file: command_storage/.gitignore
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,6 +3,9 @@ __pycache__/
|
|||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
|
# Command storage (temporary files)
|
||||||
|
command_storage/*.json
|
||||||
|
|
||||||
# Virtual environments
|
# Virtual environments
|
||||||
.venv/
|
.venv/
|
||||||
venv/
|
venv/
|
||||||
|
|||||||
58
app.py
58
app.py
@@ -8,18 +8,22 @@ from flask import Flask, render_template, jsonify, send_from_directory, request
|
|||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
# In-memory storage for commands (thread-safe)
|
# File-based storage for commands (works across multiple workers)
|
||||||
command_storage = {}
|
STORAGE_DIR = Path(__file__).parent / 'command_storage'
|
||||||
|
STORAGE_DIR.mkdir(exist_ok=True)
|
||||||
storage_lock = Lock()
|
storage_lock = Lock()
|
||||||
|
|
||||||
def cleanup_expired_commands():
|
def cleanup_expired_commands():
|
||||||
"""Remove expired commands from storage"""
|
"""Remove expired command files"""
|
||||||
with storage_lock:
|
now = datetime.now(timezone.utc)
|
||||||
now = datetime.now(timezone.utc)
|
for file_path in STORAGE_DIR.glob('*.json'):
|
||||||
expired_keys = [key for key, value in command_storage.items()
|
try:
|
||||||
if datetime.fromisoformat(value['expires_at']) < now]
|
with open(file_path, 'r') as f:
|
||||||
for key in expired_keys:
|
data = json.load(f)
|
||||||
del command_storage[key]
|
if datetime.fromisoformat(data['expires_at']) < now:
|
||||||
|
file_path.unlink()
|
||||||
|
except:
|
||||||
|
pass # Ignore corrupted files
|
||||||
|
|
||||||
# Load projects from version.json
|
# Load projects from version.json
|
||||||
def load_projects():
|
def load_projects():
|
||||||
@@ -272,13 +276,18 @@ def store_command():
|
|||||||
created_at = datetime.now(timezone.utc)
|
created_at = datetime.now(timezone.utc)
|
||||||
expires_at = created_at + timedelta(minutes=30)
|
expires_at = created_at + timedelta(minutes=30)
|
||||||
|
|
||||||
# Store command
|
# Prepare data
|
||||||
|
command_data = {
|
||||||
|
'command': command,
|
||||||
|
'created_at': created_at.isoformat(),
|
||||||
|
'expires_at': expires_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store to file
|
||||||
|
storage_file = STORAGE_DIR / f'{command_uuid}.json'
|
||||||
with storage_lock:
|
with storage_lock:
|
||||||
command_storage[command_uuid] = {
|
with open(storage_file, 'w', encoding='utf-8') as f:
|
||||||
'command': command,
|
json.dump(command_data, f)
|
||||||
'created_at': created_at.isoformat(),
|
|
||||||
'expires_at': expires_at.isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate URL - use HTTPS if behind proxy
|
# Generate URL - use HTTPS if behind proxy
|
||||||
scheme = request.headers.get('X-Forwarded-Proto', 'https' if request.is_secure else 'http')
|
scheme = request.headers.get('X-Forwarded-Proto', 'https' if request.is_secure else 'http')
|
||||||
@@ -300,20 +309,23 @@ def retrieve_command(command_uuid):
|
|||||||
"""Retrieve stored command by UUID (JSON response for plugin)"""
|
"""Retrieve stored command by UUID (JSON response for plugin)"""
|
||||||
cleanup_expired_commands()
|
cleanup_expired_commands()
|
||||||
|
|
||||||
# Keep lock for entire operation to prevent race conditions
|
storage_file = STORAGE_DIR / f'{command_uuid}.json'
|
||||||
with storage_lock:
|
|
||||||
command_data = command_storage.get(command_uuid)
|
|
||||||
|
|
||||||
if not command_data:
|
if not storage_file.exists():
|
||||||
return jsonify({'error': 'Command not found or expired'}), 404
|
return jsonify({'error': 'Command not found or expired'}), 404
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(storage_file, 'r', encoding='utf-8') as f:
|
||||||
|
command_data = json.load(f)
|
||||||
|
|
||||||
# Check if expired
|
# Check if expired
|
||||||
if datetime.fromisoformat(command_data['expires_at']) < datetime.now(timezone.utc):
|
if datetime.fromisoformat(command_data['expires_at']) < datetime.now(timezone.utc):
|
||||||
del command_storage[command_uuid]
|
storage_file.unlink()
|
||||||
return jsonify({'error': 'Command expired'}), 410
|
return jsonify({'error': 'Command expired'}), 410
|
||||||
|
|
||||||
# Return a copy to avoid modification outside lock
|
return jsonify(command_data), 200
|
||||||
return jsonify(dict(command_data)), 200
|
except Exception as e:
|
||||||
|
return jsonify({'error': 'Failed to read command'}), 500
|
||||||
|
|
||||||
# Serve /versions as JSON
|
# Serve /versions as JSON
|
||||||
@app.route('/versions')
|
@app.route('/versions')
|
||||||
|
|||||||
3
command_storage/.gitignore
vendored
Normal file
3
command_storage/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This directory stores temporary command data
|
||||||
|
# Files are automatically deleted after 30 minutes
|
||||||
|
*.json
|
||||||
Reference in New Issue
Block a user