AstraOS Ver. 1.23:
All checks were successful
Deploy AstraOS Panel / deploy (push) Successful in 12s
All checks were successful
Deploy AstraOS Panel / deploy (push) Successful in 12s
Fix: - ETS2 Download Timeout gefixt
This commit is contained in:
parent
fd977fcc01
commit
ea64ca0dcd
|
|
@ -8,12 +8,17 @@
|
|||
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">ETS2 DLCs</h2>
|
||||
</header>
|
||||
|
||||
<div class="text-sm text-gray-400">
|
||||
Große DLC-Pakete werden im Hintergrund als ZIP vorbereitet. Der Download steht erst bereit, wenn der Status <span class="text-white font-semibold">Bereit</span> ist.
|
||||
</div>
|
||||
|
||||
<div class="bg-black/40 rounded-xl backdrop-blur-lg overflow-hidden {% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
||||
<table class="min-w-full divide-y {% if maintenance_active %}divide-yellow-500/30{% else %}divide-purple-500/20{% endif %}">
|
||||
<thead class="bg-black/20">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">DLC Datum</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Status</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Erstellt von</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-300 uppercase tracking-wider">Aktionen</th>
|
||||
</tr>
|
||||
|
|
@ -23,12 +28,26 @@
|
|||
<tr class="transition {% if maintenance_active %}hover:bg-yellow-500/5{% else %}hover:bg-purple-500/5{% endif %}">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">{{ package.name }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">{{ package.dlc_date_display }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
|
||||
<span class="px-2 py-1 rounded-full text-xs font-semibold
|
||||
{% if package.archive_status == 'ready' %}bg-green-500/20 text-green-300
|
||||
{% elif package.archive_status == 'building' or package.archive_status == 'pending' %}bg-yellow-500/20 text-yellow-300
|
||||
{% else %}bg-red-500/20 text-red-300{% endif %}">
|
||||
{{ package.archive_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">{{ package.created_by or 'N/A' }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
{% if package.download_ready %}
|
||||
<a href="{{ url_for('download_ets2_dlc_package', package_id=package._id) }}" class="px-3 py-2 text-sm font-semibold rounded-lg transition text-white {% if maintenance_active %}bg-yellow-600 hover:bg-yellow-700{% else %}bg-purple-600 hover:bg-purple-700{% endif %}">
|
||||
Download
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="px-3 py-2 text-sm font-semibold rounded-lg bg-gray-700 text-gray-300 cursor-not-allowed">
|
||||
Download in Vorbereitung
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if can_manage_ets2_dlcs %}
|
||||
<form action="{{ url_for('delete_ets2_dlc_package', package_id=package._id) }}" method="POST" onsubmit="return confirm('Möchtest du dieses DLC-Paket wirklich löschen?');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
|
@ -42,7 +61,7 @@
|
|||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-8 text-center text-gray-400">Noch keine DLC-Pakete vorhanden.</td>
|
||||
<td colspan="6" class="px-6 py-8 text-center text-gray-400">Noch keine DLC-Pakete vorhanden.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from datetime import timedelta, datetime
|
|||
from functools import wraps
|
||||
from bson.objectid import ObjectId
|
||||
import paramiko
|
||||
from flask import jsonify, send_file, after_this_request
|
||||
from flask import jsonify, send_file
|
||||
import logging
|
||||
from flask_socketio import SocketIO, emit, join_room, leave_room
|
||||
import subprocess
|
||||
|
|
@ -18,7 +18,6 @@ import ipaddress
|
|||
import socket
|
||||
import ssl
|
||||
import zipfile
|
||||
import tempfile
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
async_mode = "eventlet"
|
||||
|
|
@ -38,7 +37,11 @@ except Exception as e:
|
|||
os.makedirs(BOT_UPLOAD_FOLDER, exist_ok=True)
|
||||
print(f"WARNUNG: Konnte '/opt/astraos/Discord_Bots' nicht erstellen ({e}), benutze lokalen Ordner: {BOT_UPLOAD_FOLDER}")
|
||||
app.config['BOT_UPLOAD_FOLDER'] = BOT_UPLOAD_FOLDER
|
||||
ETS2_DLC_ARCHIVE_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ets2_dlc_archives')
|
||||
os.makedirs(ETS2_DLC_ARCHIVE_FOLDER, exist_ok=True)
|
||||
running_bots = {}
|
||||
ets2_dlc_build_locks = {}
|
||||
ets2_dlc_build_locks_guard = threading.Lock()
|
||||
class SuppressServerLoadFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
msg = record.getMessage()
|
||||
|
|
@ -275,6 +278,156 @@ def add_log(action, details=None):
|
|||
}
|
||||
logs_collection.insert_one(log_entry)
|
||||
|
||||
def _ets2_archive_filename(package_id: str, package_name: str):
|
||||
safe_name = secure_filename(package_name or 'ets2_dlc_package') or 'ets2_dlc_package'
|
||||
return f'{package_id}_{safe_name}.zip'
|
||||
|
||||
|
||||
def _ets2_archive_path(package_id: str, package_name: str):
|
||||
return os.path.join(ETS2_DLC_ARCHIVE_FOLDER, _ets2_archive_filename(package_id, package_name))
|
||||
|
||||
|
||||
def _get_ets2_build_lock(package_id: str):
|
||||
with ets2_dlc_build_locks_guard:
|
||||
lock = ets2_dlc_build_locks.get(package_id)
|
||||
if lock is None:
|
||||
lock = threading.Lock()
|
||||
ets2_dlc_build_locks[package_id] = lock
|
||||
return lock
|
||||
|
||||
|
||||
def _set_ets2_archive_state(package_id: str, **fields):
|
||||
if ets2_dlcs_collection is None:
|
||||
return
|
||||
ets2_dlcs_collection.update_one({'_id': ObjectId(package_id)}, {'$set': fields})
|
||||
|
||||
|
||||
def _build_ets2_archive(package_id: str):
|
||||
if ets2_dlcs_collection is None:
|
||||
return
|
||||
|
||||
package = ets2_dlcs_collection.find_one({'_id': ObjectId(package_id)})
|
||||
if not package:
|
||||
return
|
||||
|
||||
folder_path = package.get('folder_path')
|
||||
if not folder_path or not os.path.isdir(folder_path):
|
||||
_set_ets2_archive_state(
|
||||
package_id,
|
||||
archive_status='error',
|
||||
archive_error='Ordnerpfad ist nicht mehr verfügbar.',
|
||||
archive_generated_at=None,
|
||||
archive_path=None
|
||||
)
|
||||
return
|
||||
|
||||
archive_path = _ets2_archive_path(package_id, package.get('name', 'ets2_dlc_package'))
|
||||
temp_archive_path = f'{archive_path}.tmp'
|
||||
|
||||
try:
|
||||
if os.path.exists(temp_archive_path):
|
||||
try:
|
||||
os.remove(temp_archive_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_set_ets2_archive_state(
|
||||
package_id,
|
||||
archive_status='building',
|
||||
archive_error=None,
|
||||
archive_path=archive_path,
|
||||
archive_generated_at=None
|
||||
)
|
||||
|
||||
with zipfile.ZipFile(temp_archive_path, 'w', compression=zipfile.ZIP_STORED, allowZip64=True) as zipf:
|
||||
has_content = False
|
||||
for root, _, files in os.walk(folder_path):
|
||||
for filename in files:
|
||||
file_path = os.path.join(root, filename)
|
||||
arcname = os.path.relpath(file_path, folder_path)
|
||||
zipf.write(file_path, arcname, compress_type=zipfile.ZIP_STORED)
|
||||
has_content = True
|
||||
|
||||
if not has_content:
|
||||
zipf.writestr('EMPTY_PACKAGE.txt', 'Dieses DLC-Paket enthaelt aktuell keine Dateien.')
|
||||
|
||||
os.replace(temp_archive_path, archive_path)
|
||||
_set_ets2_archive_state(
|
||||
package_id,
|
||||
archive_status='ready',
|
||||
archive_error=None,
|
||||
archive_path=archive_path,
|
||||
archive_generated_at=datetime.utcnow()
|
||||
)
|
||||
except Exception as e:
|
||||
try:
|
||||
if os.path.exists(temp_archive_path):
|
||||
os.remove(temp_archive_path)
|
||||
except Exception:
|
||||
pass
|
||||
_set_ets2_archive_state(
|
||||
package_id,
|
||||
archive_status='error',
|
||||
archive_error=str(e),
|
||||
archive_generated_at=None,
|
||||
archive_path=archive_path
|
||||
)
|
||||
|
||||
|
||||
def _schedule_ets2_archive_build(package_id: str):
|
||||
lock = _get_ets2_build_lock(package_id)
|
||||
if not lock.acquire(blocking=False):
|
||||
return False
|
||||
|
||||
def runner():
|
||||
try:
|
||||
_build_ets2_archive(package_id)
|
||||
finally:
|
||||
try:
|
||||
lock.release()
|
||||
except Exception:
|
||||
pass
|
||||
with ets2_dlc_build_locks_guard:
|
||||
ets2_dlc_build_locks.pop(package_id, None)
|
||||
|
||||
thread = threading.Thread(target=runner, daemon=True)
|
||||
thread.start()
|
||||
return True
|
||||
|
||||
|
||||
def _format_ets2_dlcs_for_view(packages):
|
||||
for package in packages:
|
||||
dlc_date = package.get('dlc_date')
|
||||
try:
|
||||
package['dlc_date_display'] = datetime.strptime(dlc_date, '%Y-%m-%d').strftime('%d.%m.%Y') if dlc_date else 'N/A'
|
||||
except ValueError:
|
||||
package['dlc_date_display'] = str(dlc_date) if dlc_date else 'N/A'
|
||||
|
||||
package_id = str(package['_id'])
|
||||
archive_path = package.get('archive_path') or _ets2_archive_path(package_id, package.get('name', 'ets2_dlc_package'))
|
||||
archive_ready = package.get('archive_status') == 'ready' and os.path.isfile(archive_path)
|
||||
folder_path = package.get('folder_path')
|
||||
|
||||
if not archive_ready and folder_path and os.path.isdir(folder_path):
|
||||
package.setdefault('archive_status', 'pending')
|
||||
package['download_ready'] = False
|
||||
package['archive_status_display'] = 'Wird erstellt'
|
||||
package['archive_path'] = archive_path
|
||||
_schedule_ets2_archive_build(package_id)
|
||||
elif archive_ready:
|
||||
package['download_ready'] = True
|
||||
package['archive_status_display'] = 'Bereit'
|
||||
package['archive_path'] = archive_path
|
||||
else:
|
||||
package['download_ready'] = False
|
||||
package['archive_status_display'] = 'Fehler'
|
||||
package['archive_path'] = archive_path
|
||||
|
||||
if package.get('archive_status') == 'error' and package.get('archive_error'):
|
||||
package['archive_status_display'] = f"Fehler: {package.get('archive_error')}"
|
||||
|
||||
return packages
|
||||
|
||||
def get_user_permissions(user_id):
|
||||
user = accounts_collection.find_one({'_id': ObjectId(user_id)})
|
||||
if not user or 'role_id' not in user:
|
||||
|
|
@ -997,13 +1150,7 @@ def ets2_dlcs():
|
|||
flash("Datenbankverbindung nicht verfügbar.", "danger")
|
||||
return redirect(url_for('dashboard'))
|
||||
|
||||
packages = list(ets2_dlcs_collection.find().sort('created_at', -1))
|
||||
for package in packages:
|
||||
dlc_date = package.get('dlc_date')
|
||||
try:
|
||||
package['dlc_date_display'] = datetime.strptime(dlc_date, '%Y-%m-%d').strftime('%d.%m.%Y') if dlc_date else 'N/A'
|
||||
except ValueError:
|
||||
package['dlc_date_display'] = str(dlc_date) if dlc_date else 'N/A'
|
||||
packages = _format_ets2_dlcs_for_view(list(ets2_dlcs_collection.find().sort('created_at', -1)))
|
||||
return render_template(
|
||||
'ets2_dlcs.html',
|
||||
active_page='ets2_dlcs',
|
||||
|
|
@ -1043,15 +1190,20 @@ def create_ets2_dlc_package():
|
|||
flash("Ein DLC-Paket mit diesem Namen existiert bereits.", "danger")
|
||||
return redirect(url_for('ets2_dlcs'))
|
||||
|
||||
ets2_dlcs_collection.insert_one({
|
||||
package_id = ets2_dlcs_collection.insert_one({
|
||||
'name': package_name,
|
||||
'folder_path': folder_path,
|
||||
'dlc_date': dlc_date,
|
||||
'created_by': session.get('username'),
|
||||
'created_at': datetime.utcnow()
|
||||
})
|
||||
'created_at': datetime.utcnow(),
|
||||
'archive_status': 'pending',
|
||||
'archive_path': None,
|
||||
'archive_generated_at': None,
|
||||
'archive_error': None
|
||||
}).inserted_id
|
||||
add_log("ETS2 DLC-Paket erstellt", f"Name: {package_name}, Pfad: {folder_path}")
|
||||
flash("DLC-Paket wurde erfolgreich erstellt.", "success")
|
||||
_schedule_ets2_archive_build(str(package_id))
|
||||
flash("DLC-Paket wurde erstellt. Das Archiv wird im Hintergrund vorbereitet.", "success")
|
||||
return redirect(url_for('ets2_dlcs'))
|
||||
|
||||
|
||||
|
|
@ -1074,6 +1226,13 @@ def delete_ets2_dlc_package(package_id):
|
|||
flash("DLC-Paket nicht gefunden.", "danger")
|
||||
return redirect(url_for('ets2_dlcs'))
|
||||
|
||||
archive_path = package.get('archive_path') or _ets2_archive_path(package_id, package.get('name', 'ets2_dlc_package'))
|
||||
if package.get('archive_status') == 'ready' and os.path.isfile(archive_path):
|
||||
try:
|
||||
os.remove(archive_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ets2_dlcs_collection.delete_one({'_id': package_object_id})
|
||||
add_log("ETS2 DLC-Paket gelöscht", f"Name: {package.get('name', 'N/A')}")
|
||||
flash("DLC-Paket wurde gelöscht.", "success")
|
||||
|
|
@ -1099,46 +1258,18 @@ def download_ets2_dlc_package(package_id):
|
|||
flash("DLC-Paket nicht gefunden.", "danger")
|
||||
return redirect(url_for('ets2_dlcs'))
|
||||
|
||||
folder_path = package.get('folder_path')
|
||||
if not folder_path or not os.path.isdir(folder_path):
|
||||
flash("Der gespeicherte Ordnerpfad ist ungültig oder nicht mehr vorhanden.", "danger")
|
||||
archive_path = package.get('archive_path') or _ets2_archive_path(package_id, package.get('name', 'ets2_dlc_package'))
|
||||
if package.get('archive_status') != 'ready' or not os.path.isfile(archive_path):
|
||||
if package.get('folder_path') and os.path.isdir(package.get('folder_path')):
|
||||
_schedule_ets2_archive_build(package_id)
|
||||
flash("Das DLC-Archiv wird noch erstellt. Bitte in ein paar Augenblicken erneut versuchen.", "info")
|
||||
else:
|
||||
flash("Das gespeicherte Archiv ist nicht verfügbar.", "danger")
|
||||
return redirect(url_for('ets2_dlcs'))
|
||||
|
||||
temp_zip = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
|
||||
temp_zip_path = temp_zip.name
|
||||
temp_zip.close()
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(temp_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
has_content = False
|
||||
for root, _, files in os.walk(folder_path):
|
||||
for filename in files:
|
||||
has_content = True
|
||||
file_path = os.path.join(root, filename)
|
||||
arcname = os.path.relpath(file_path, folder_path)
|
||||
zipf.write(file_path, arcname)
|
||||
|
||||
if not has_content:
|
||||
zipf.writestr('EMPTY_PACKAGE.txt', 'Dieses DLC-Paket enthaelt aktuell keine Dateien.')
|
||||
except Exception as e:
|
||||
try:
|
||||
os.remove(temp_zip_path)
|
||||
except Exception:
|
||||
pass
|
||||
flash(f"Fehler beim Erstellen des Downloads: {e}", "danger")
|
||||
return redirect(url_for('ets2_dlcs'))
|
||||
|
||||
@after_this_request
|
||||
def cleanup_temp_file(response):
|
||||
try:
|
||||
os.remove(temp_zip_path)
|
||||
except Exception:
|
||||
pass
|
||||
return response
|
||||
|
||||
safe_name = secure_filename(package.get('name', 'ets2_dlc_package')) or 'ets2_dlc_package'
|
||||
add_log("ETS2 DLC-Paket heruntergeladen", f"Name: {package.get('name', 'N/A')}")
|
||||
return send_file(temp_zip_path, as_attachment=True, download_name=f"{safe_name}.zip")
|
||||
return send_file(archive_path, as_attachment=True, download_name=f"{safe_name}.zip", conditional=True)
|
||||
|
||||
def stream_bot_output(bot_id, process):
|
||||
def stream_to_socket(pipe, log_type):
|
||||
|
|
|
|||
Loading…
Reference in a new issue